@elizaos/app-core 2.0.0-alpha.335 → 2.0.0-alpha.337
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/app-lifeops/src/routes/lifeops-routes.d.ts.map +1 -1
- package/apps/app-lifeops/src/routes/lifeops-routes.js +10 -1
- package/i18n/locales/en.json +5 -5
- package/package.json +5 -5
- package/packages/agent/src/runtime/eliza.d.ts.map +1 -1
- package/packages/agent/src/runtime/eliza.js +7 -2
- package/packages/app-core/src/api/automations-compat-routes.js +1 -1
- package/packages/app-core/src/api/client-n8n.d.ts +4 -1
- package/packages/app-core/src/api/client-n8n.d.ts.map +1 -1
- package/packages/app-core/src/api/client-n8n.js +18 -0
- package/packages/app-core/src/api/client-types-chat.d.ts +37 -0
- package/packages/app-core/src/api/client-types-chat.d.ts.map +1 -1
- package/packages/app-core/src/api/n8n-routes.d.ts +6 -0
- package/packages/app-core/src/api/n8n-routes.d.ts.map +1 -1
- package/packages/app-core/src/api/n8n-routes.js +334 -0
- package/packages/app-core/src/components/character/CharacterEditorPanels.d.ts.map +1 -1
- package/packages/app-core/src/components/character/CharacterEditorPanels.js +192 -85
- package/packages/app-core/src/components/character/CharacterExperienceWorkspace.d.ts.map +1 -1
- package/packages/app-core/src/components/character/CharacterExperienceWorkspace.js +182 -31
- package/packages/app-core/src/components/character/CharacterHubView.d.ts.map +1 -1
- package/packages/app-core/src/components/character/CharacterHubView.js +253 -23
- package/packages/app-core/src/components/character/CharacterOverviewSection.d.ts +14 -1
- package/packages/app-core/src/components/character/CharacterOverviewSection.d.ts.map +1 -1
- package/packages/app-core/src/components/character/CharacterOverviewSection.js +35 -8
- package/packages/app-core/src/components/character/character-hub-helpers.d.ts.map +1 -1
- package/packages/app-core/src/components/character/character-hub-helpers.js +3 -7
- package/packages/app-core/src/components/pages/AutomationsView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/AutomationsView.js +523 -274
- package/packages/app-core/src/components/pages/HeartbeatForm.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/HeartbeatForm.js +11 -13
- package/packages/app-core/src/components/pages/KnowledgeView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/KnowledgeView.js +20 -6
- package/packages/app-core/src/components/pages/RelationshipsGraphPanel.d.ts +2 -1
- package/packages/app-core/src/components/pages/RelationshipsGraphPanel.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/RelationshipsGraphPanel.js +3 -3
- package/packages/app-core/src/components/pages/WorkflowGraphViewer.d.ts +0 -9
- package/packages/app-core/src/components/pages/WorkflowGraphViewer.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/WorkflowGraphViewer.js +52 -26
- package/packages/app-core/src/components/pages/knowledge-detail.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/knowledge-detail.js +28 -2
- package/packages/app-core/src/components/pages/page-scoped-conversations.d.ts +1 -1
- package/packages/app-core/src/components/pages/page-scoped-conversations.js +4 -4
- package/packages/app-core/src/components/pages/relationships/RelationshipsPersonPanels.d.ts +2 -1
- package/packages/app-core/src/components/pages/relationships/RelationshipsPersonPanels.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/relationships/RelationshipsPersonPanels.js +69 -32
- package/packages/app-core/src/components/pages/relationships/RelationshipsSidebar.d.ts +1 -6
- package/packages/app-core/src/components/pages/relationships/RelationshipsSidebar.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/relationships/RelationshipsSidebar.js +4 -4
- package/packages/app-core/src/components/pages/relationships/RelationshipsWorkspaceView.d.ts +2 -1
- package/packages/app-core/src/components/pages/relationships/RelationshipsWorkspaceView.d.ts.map +1 -1
- package/packages/app-core/src/components/pages/relationships/RelationshipsWorkspaceView.js +25 -15
- package/packages/app-core/src/i18n/locales/en.json +5 -5
- package/packages/app-core/src/registry/generate-apps.js +2 -2
- package/packages/app-core/src/runtime/eliza.d.ts.map +1 -1
- package/packages/app-core/src/runtime/eliza.js +6 -1
- package/packages/app-core/src/services/n8n-dispatch.d.ts +10 -2
- package/packages/app-core/src/services/n8n-dispatch.d.ts.map +1 -1
- package/packages/app-core/src/services/n8n-dispatch.js +102 -8
- package/packages/shared/src/contracts/lifeops.d.ts +4 -0
- package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
- package/packages/typescript/src/features/advanced-capabilities/clipboard/services/clipboardService.d.ts.map +1 -1
- package/packages/typescript/src/features/advanced-capabilities/clipboard/services/clipboardService.js +6 -1
|
@@ -42,6 +42,8 @@ const NODE_CLASS_ORDER = [
|
|
|
42
42
|
];
|
|
43
43
|
const PAGE_CHAT_PREFILL_EVENT = "milady:chat:prefill";
|
|
44
44
|
const DESCRIBE_WORKFLOW_PROMPT = "Describe your workflow";
|
|
45
|
+
const WORKFLOW_PROMPT_PLACEHOLDER = "Describe the trigger and steps, e.g. when a GitHub issue opens, summarize it and post to Discord";
|
|
46
|
+
const AUTOMATIONS_OVERVIEW_VISIBILITY_EVENT = "milady:automations:overview-visibility";
|
|
45
47
|
function createWorkflowDraftId() {
|
|
46
48
|
return globalThis.crypto.randomUUID();
|
|
47
49
|
}
|
|
@@ -55,6 +57,24 @@ function prefillPageChat(text, options) {
|
|
|
55
57
|
},
|
|
56
58
|
}));
|
|
57
59
|
}
|
|
60
|
+
function buildWorkflowCopyRequest(workflow, name) {
|
|
61
|
+
return {
|
|
62
|
+
name,
|
|
63
|
+
nodes: workflow.nodes?.map((node) => ({
|
|
64
|
+
name: node.name,
|
|
65
|
+
type: node.type,
|
|
66
|
+
typeVersion: node.typeVersion ?? 1,
|
|
67
|
+
position: node.position ?? [0, 0],
|
|
68
|
+
parameters: node.parameters ?? {},
|
|
69
|
+
...(node.notes ? { notes: node.notes } : {}),
|
|
70
|
+
...(node.notesInFlow !== undefined
|
|
71
|
+
? { notesInFlow: node.notesInFlow }
|
|
72
|
+
: {}),
|
|
73
|
+
})) ?? [],
|
|
74
|
+
connections: workflow.connections ?? {},
|
|
75
|
+
settings: {},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
58
78
|
// Reads `#automations.trigger=<id>` from the URL hash. The LifeOps chat-sidebar
|
|
59
79
|
// Automations widget writes this when a row is clicked, so /automations can
|
|
60
80
|
// focus the matching trigger card on navigation. Duplicated here (instead of
|
|
@@ -199,6 +219,35 @@ function getAutomationIndicatorTone(item) {
|
|
|
199
219
|
}
|
|
200
220
|
return undefined;
|
|
201
221
|
}
|
|
222
|
+
function getAutomationStatusTone(item) {
|
|
223
|
+
if (item.isDraft)
|
|
224
|
+
return "warning";
|
|
225
|
+
if (item.type === "n8n_workflow") {
|
|
226
|
+
return item.enabled ? "success" : "muted";
|
|
227
|
+
}
|
|
228
|
+
if (item.trigger) {
|
|
229
|
+
const lastTone = toneForLastStatus(item.trigger.lastStatus);
|
|
230
|
+
if (lastTone === "danger")
|
|
231
|
+
return "danger";
|
|
232
|
+
return item.trigger.enabled ? "success" : "muted";
|
|
233
|
+
}
|
|
234
|
+
if (item.task) {
|
|
235
|
+
return item.task.isCompleted ? "muted" : "success";
|
|
236
|
+
}
|
|
237
|
+
return "muted";
|
|
238
|
+
}
|
|
239
|
+
function getTriggerWakeModeLabel(trigger) {
|
|
240
|
+
return trigger.wakeMode === "inject_now"
|
|
241
|
+
? "Interrupt and run now"
|
|
242
|
+
: "Queue for next cycle";
|
|
243
|
+
}
|
|
244
|
+
function getTriggerStartModeLabel(trigger) {
|
|
245
|
+
if (trigger.triggerType === "once")
|
|
246
|
+
return "One time";
|
|
247
|
+
if (trigger.triggerType === "cron")
|
|
248
|
+
return "Cron schedule";
|
|
249
|
+
return "Repeating";
|
|
250
|
+
}
|
|
202
251
|
function buildTriggerSchedulePrompt(trigger) {
|
|
203
252
|
if (trigger.triggerType === "interval") {
|
|
204
253
|
return `Schedule: interval every ${trigger.intervalMs ?? 0}ms.`;
|
|
@@ -236,31 +285,6 @@ function buildWorkflowCompilationPrompt(item) {
|
|
|
236
285
|
lines.push("Ask follow-up questions only when workflow intent is genuinely ambiguous.");
|
|
237
286
|
return lines.join("\n");
|
|
238
287
|
}
|
|
239
|
-
function buildWorkflowDuplicationPrompt(item) {
|
|
240
|
-
const lines = [
|
|
241
|
-
"Duplicate this n8n workflow into a new workflow draft.",
|
|
242
|
-
`Existing workflow name: ${item.title}`,
|
|
243
|
-
`Description: ${item.description || "No description provided."}`,
|
|
244
|
-
"Recreate the same workflow structure, preserving the intent, nodes, and connections.",
|
|
245
|
-
];
|
|
246
|
-
if (item.schedules.length > 0) {
|
|
247
|
-
lines.push("Preserve these schedules on the new workflow:");
|
|
248
|
-
for (const schedule of item.schedules) {
|
|
249
|
-
lines.push(`- ${buildTriggerSchedulePrompt(schedule)}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (item.workflow) {
|
|
253
|
-
lines.push("Existing workflow JSON:");
|
|
254
|
-
lines.push(JSON.stringify({
|
|
255
|
-
id: item.workflow.id,
|
|
256
|
-
name: item.workflow.name,
|
|
257
|
-
description: item.workflow.description,
|
|
258
|
-
nodes: item.workflow.nodes ?? [],
|
|
259
|
-
connections: item.workflow.connections ?? {},
|
|
260
|
-
}, null, 2));
|
|
261
|
-
}
|
|
262
|
-
return lines.join("\n");
|
|
263
|
-
}
|
|
264
288
|
function getNodeClassLabel(className) {
|
|
265
289
|
switch (className) {
|
|
266
290
|
case "agent":
|
|
@@ -558,9 +582,7 @@ function useAutomationsViewController() {
|
|
|
558
582
|
setEditorOpen(true);
|
|
559
583
|
};
|
|
560
584
|
const openCreateTask = () => {
|
|
561
|
-
|
|
562
|
-
setEditorMode("task");
|
|
563
|
-
setEditorOpen(true);
|
|
585
|
+
openCreateTrigger();
|
|
564
586
|
};
|
|
565
587
|
const openEditTrigger = (trigger) => {
|
|
566
588
|
setEditingId(trigger.id);
|
|
@@ -591,8 +613,14 @@ function useAutomationsViewController() {
|
|
|
591
613
|
if (editingId) {
|
|
592
614
|
const updated = await updateTrigger(editingId, buildUpdateRequest(form));
|
|
593
615
|
if (updated) {
|
|
594
|
-
|
|
595
|
-
|
|
616
|
+
if (updated.kind === "workflow" && updated.workflowId) {
|
|
617
|
+
setSelectedItemId(`workflow:${updated.workflowId}`);
|
|
618
|
+
setSelectedItemKind("workflow");
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
setSelectedItemId(`trigger:${updated.id}`);
|
|
622
|
+
setSelectedItemKind("trigger");
|
|
623
|
+
}
|
|
596
624
|
await refreshAutomations();
|
|
597
625
|
closeEditor();
|
|
598
626
|
}
|
|
@@ -600,8 +628,14 @@ function useAutomationsViewController() {
|
|
|
600
628
|
}
|
|
601
629
|
const created = await createTrigger(buildCreateRequest(form));
|
|
602
630
|
if (created) {
|
|
603
|
-
|
|
604
|
-
|
|
631
|
+
if (created.kind === "workflow" && created.workflowId) {
|
|
632
|
+
setSelectedItemId(`workflow:${created.workflowId}`);
|
|
633
|
+
setSelectedItemKind("workflow");
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
setSelectedItemId(`trigger:${created.id}`);
|
|
637
|
+
setSelectedItemKind("trigger");
|
|
638
|
+
}
|
|
605
639
|
void loadTriggerRuns(created.id);
|
|
606
640
|
await refreshAutomations();
|
|
607
641
|
closeEditor();
|
|
@@ -715,17 +749,21 @@ function useAutomationsViewController() {
|
|
|
715
749
|
return allItems[0] ?? null;
|
|
716
750
|
}, [allItems, editingId, editingTaskId, editorOpen, selectedItemId]);
|
|
717
751
|
const modalTitle = editorMode === "trigger"
|
|
718
|
-
?
|
|
719
|
-
?
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
752
|
+
? form.kind === "workflow"
|
|
753
|
+
? editingId
|
|
754
|
+
? `Edit ${form.displayName.trim() || "schedule"}`
|
|
755
|
+
: "New schedule"
|
|
756
|
+
: editingId
|
|
757
|
+
? t("heartbeatsview.editTitle", {
|
|
758
|
+
name: form.displayName.trim() ||
|
|
759
|
+
t("automations.taskLabel", {
|
|
760
|
+
defaultValue: "Task",
|
|
761
|
+
}),
|
|
762
|
+
defaultValue: "Edit {{name}}",
|
|
763
|
+
})
|
|
764
|
+
: t("automations.newTask", {
|
|
765
|
+
defaultValue: "New task",
|
|
766
|
+
})
|
|
729
767
|
: editingTaskId
|
|
730
768
|
? t("automations.editTask", {
|
|
731
769
|
defaultValue: "Edit task",
|
|
@@ -760,6 +798,7 @@ function useAutomationsViewController() {
|
|
|
760
798
|
editorOpen,
|
|
761
799
|
setEditorOpen,
|
|
762
800
|
editorMode,
|
|
801
|
+
setEditorMode,
|
|
763
802
|
formError,
|
|
764
803
|
setFormError,
|
|
765
804
|
editorEnabled,
|
|
@@ -895,10 +934,8 @@ function WorkflowTemplatesModal({ open, onOpenChange, onSelectTemplate, onSelect
|
|
|
895
934
|
defaultValue: "Use template",
|
|
896
935
|
}) })] })] })] }) }));
|
|
897
936
|
}
|
|
898
|
-
function CreateAutomationDialog({ open,
|
|
899
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "w-[min(calc(100vw-1.5rem),34rem)] max-w-none", children: [
|
|
900
|
-
? "Choose a simple task or a task with a schedule."
|
|
901
|
-
: "Choose whether you want a task or a workflow." })] }), _jsxs("div", { className: `grid gap-3 ${mode === "tasks" ? "sm:grid-cols-2" : "sm:grid-cols-3"}`, children: [_jsxs("button", { type: "button", onClick: onCreateTask, className: "rounded-xl border border-border/30 bg-bg/30 p-4 text-left transition-colors hover:border-accent/40 hover:bg-accent/5", children: [_jsx("div", { className: "text-sm font-semibold text-txt", children: "Task" }), _jsx("div", { className: "mt-1 text-xs-tight text-muted/80", children: "A simple text editor for something the agent should work on." })] }), _jsxs("button", { type: "button", onClick: onCreateScheduledTask, className: "rounded-xl border border-border/30 bg-bg/30 p-4 text-left transition-colors hover:border-accent/40 hover:bg-accent/5", children: [_jsx("div", { className: "text-sm font-semibold text-txt", children: "Task with schedule" }), _jsx("div", { className: "mt-1 text-xs-tight text-muted/80", children: "A text task that runs on a schedule instead of a workflow." })] }), mode === "all" ? (_jsxs("button", { type: "button", onClick: onCreateWorkflow, className: "rounded-xl border border-border/30 bg-bg/30 p-4 text-left transition-colors hover:border-accent/40 hover:bg-accent/5", children: [_jsx("div", { className: "text-sm font-semibold text-txt", children: "Workflow" }), _jsx("div", { className: "mt-1 text-xs-tight text-muted/80", children: "Open a graph-based workflow draft and wire the steps visually." })] })) : null] })] }) }));
|
|
937
|
+
function CreateAutomationDialog({ open, onOpenChange, onCreateTask, onCreateWorkflow, }) {
|
|
938
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "w-[min(calc(100vw-1.5rem),34rem)] max-w-none", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Create automation" }) }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx("button", { type: "button", onClick: onCreateTask, className: "rounded-xl border border-border/30 bg-bg/30 p-4 text-left transition-colors hover:border-accent/40 hover:bg-accent/5", children: _jsx("div", { className: "text-sm font-semibold text-txt", children: "Task" }) }), _jsx("button", { type: "button", onClick: onCreateWorkflow, className: "rounded-xl border border-border/30 bg-bg/30 p-4 text-left transition-colors hover:border-accent/40 hover:bg-accent/5", children: _jsx("div", { className: "text-sm font-semibold text-txt", children: "Workflow" }) })] })] }) }));
|
|
902
939
|
}
|
|
903
940
|
// ---------------------------------------------------------------------------
|
|
904
941
|
// Zero-state onboarding CTA (Item 9)
|
|
@@ -917,11 +954,7 @@ function AutomationsZeroState({ onBrowseTemplates, onNewTrigger, onNewTask, }) {
|
|
|
917
954
|
}
|
|
918
955
|
function TaskForm() {
|
|
919
956
|
const { taskFormName, setTaskFormName, taskFormDescription, setTaskFormDescription, editingTaskId, formError, taskSaving, onSubmitTask, onDeleteTask, closeEditor, modalTitle, t, } = useAutomationsViewContext();
|
|
920
|
-
return (_jsxs(PagePanel, { variant: "padded", className: "space-y-5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-lg font-semibold text-txt", children: modalTitle }), _jsx(Button, { variant: "ghost", size: "sm", onClick: closeEditor, children: t("common.cancel") })] }), formError && (_jsx("div", { className: "rounded-lg border border-danger/20 bg-danger/10 p-3 text-sm text-danger", children: formError })), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(FieldLabel, { children:
|
|
921
|
-
defaultValue: "Task name...",
|
|
922
|
-
}), autoFocus: true })] }), _jsxs("div", { children: [_jsx(FieldLabel, { children: t("common.description", { defaultValue: "Description" }) }), _jsx(Textarea, { value: taskFormDescription, onChange: (event) => setTaskFormDescription(event.target.value), placeholder: t("automations.taskDescriptionPlaceholder", {
|
|
923
|
-
defaultValue: "What should this task do...",
|
|
924
|
-
}), rows: 4 })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "default", size: "sm", disabled: taskSaving || !taskFormName.trim(), onClick: () => void onSubmitTask(), children: editingTaskId
|
|
957
|
+
return (_jsxs(PagePanel, { variant: "padded", className: "space-y-5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-lg font-semibold text-txt", children: modalTitle }), _jsx(Button, { variant: "ghost", size: "sm", onClick: closeEditor, children: t("common.cancel") })] }), formError && (_jsx("div", { className: "rounded-lg border border-danger/20 bg-danger/10 p-3 text-sm text-danger", children: formError })), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(FieldLabel, { children: "Task name" }), _jsx(Input, { value: taskFormName, onChange: (event) => setTaskFormName(event.target.value), placeholder: "Task name...", autoFocus: true })] }), _jsxs("div", { children: [_jsx(FieldLabel, { children: "Prompt" }), _jsx(Textarea, { value: taskFormDescription, onChange: (event) => setTaskFormDescription(event.target.value), placeholder: "What should this task do?", rows: 4 })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "default", size: "sm", disabled: taskSaving || !taskFormName.trim(), onClick: () => void onSubmitTask(), children: editingTaskId
|
|
925
958
|
? t("automations.saveTask", {
|
|
926
959
|
defaultValue: "Save task",
|
|
927
960
|
})
|
|
@@ -972,7 +1005,7 @@ function AutomationNodeCatalogPane({ nodes, }) {
|
|
|
972
1005
|
return _jsx(AutomationNodePalette, { nodes: nodes, title: "Nodes" });
|
|
973
1006
|
}
|
|
974
1007
|
function TaskAutomationDetailPane({ automation, onPromoteToWorkflow, }) {
|
|
975
|
-
const { openEditTask, onDeleteTask, onToggleTaskCompleted, setEditorOpen, setTaskFormDescription, setTaskFormName, setEditingTaskId, setSelectedItemId, setSelectedItemKind, t, uiLanguage, } = useAutomationsViewContext();
|
|
1008
|
+
const { openEditTask, onDeleteTask, onToggleTaskCompleted, setEditorOpen, setEditorMode, setTaskFormDescription, setTaskFormName, setEditingTaskId, setSelectedItemId, setSelectedItemKind, t, uiLanguage, } = useAutomationsViewContext();
|
|
976
1009
|
const task = automation.task;
|
|
977
1010
|
if (!task) {
|
|
978
1011
|
return null;
|
|
@@ -981,28 +1014,59 @@ function TaskAutomationDetailPane({ automation, onPromoteToWorkflow, }) {
|
|
|
981
1014
|
? "System"
|
|
982
1015
|
: task.isCompleted
|
|
983
1016
|
? "Completed"
|
|
984
|
-
: "
|
|
1017
|
+
: "Open";
|
|
985
1018
|
const statusTone = automation.system ? "muted" : task.isCompleted ? "muted" : "success";
|
|
986
|
-
|
|
1019
|
+
const nextScheduledRun = automation.schedules
|
|
1020
|
+
.map((schedule) => schedule.nextRunAtMs ?? 0)
|
|
1021
|
+
.filter((value) => value > 0)
|
|
1022
|
+
.sort((left, right) => left - right)[0];
|
|
1023
|
+
const taskTypeLabel = automation.system ? "Agent owned" : "Task";
|
|
1024
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(DetailHeader, { icon: automation.system ? (_jsx(Settings, { className: "h-3.5 w-3.5", "aria-hidden": true })) : (_jsx(FileText, { className: "h-3.5 w-3.5", "aria-hidden": true })), title: getAutomationDisplayTitle(automation), description: automation.description ||
|
|
1025
|
+
(automation.system ? "Agent-owned manual task." : "Simple text task."), status: _jsx(DetailStatusIndicator, { label: statusLabel, tone: statusTone, dotOnly: !automation.system && !task.isCompleted }), actions: !automation.system ? (_jsxs(_Fragment, { children: [_jsx(IconAction, { label: task.isCompleted ? "Reopen" : "Complete", onClick: () => void onToggleTaskCompleted(task.id, task.isCompleted), icon: task.isCompleted ? (_jsx(Circle, { className: "h-3.5 w-3.5" })) : (_jsx(CheckCircle2, { className: "h-3.5 w-3.5" })), tone: task.isCompleted ? "ok" : undefined }), _jsx(IconAction, { label: "Duplicate", onClick: () => {
|
|
987
1026
|
setTaskFormName(`${task.name} copy`);
|
|
988
1027
|
setTaskFormDescription(task.description);
|
|
989
1028
|
setEditingTaskId(null);
|
|
1029
|
+
setEditorMode("task");
|
|
990
1030
|
setSelectedItemId(null);
|
|
991
1031
|
setSelectedItemKind(null);
|
|
992
1032
|
setEditorOpen(true);
|
|
993
|
-
}, icon: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: "Compile to Workflow", onClick: () => void onPromoteToWorkflow(automation), icon: _jsx(GitBranch, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: t("triggersview.Edit"), onClick: () => openEditTask(task), icon: _jsx(EditIcon, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: t("triggersview.Delete"), onClick: () => void onDeleteTask(task.id), icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), tone: "danger" })] })) : null }), _jsx(
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1033
|
+
}, icon: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: "Compile to Workflow", onClick: () => void onPromoteToWorkflow(automation), icon: _jsx(GitBranch, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: t("triggersview.Edit"), onClick: () => openEditTask(task), icon: _jsx(EditIcon, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: t("triggersview.Delete"), onClick: () => void onDeleteTask(task.id), icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), tone: "danger" })] })) : null }), _jsxs("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-4", children: [_jsx(OverviewMetricCard, { label: "Task type", value: taskTypeLabel, detail: automation.schedules.length > 0
|
|
1034
|
+
? formatScheduleCount(automation.schedules.length)
|
|
1035
|
+
: "Run it manually" }), _jsx(OverviewMetricCard, { label: "Status", value: statusLabel, detail: task.isCompleted ? "Already completed" : "Still open", tone: task.isCompleted ? "default" : "ok" }), _jsx(OverviewMetricCard, { label: "Starts", value: nextScheduledRun
|
|
1036
|
+
? formatRelativeFuture(nextScheduledRun, t)
|
|
1037
|
+
: "Manual", detail: nextScheduledRun
|
|
1038
|
+
? formatDateTime(nextScheduledRun, {
|
|
1039
|
+
fallback: "—",
|
|
1040
|
+
locale: uiLanguage,
|
|
1041
|
+
})
|
|
1042
|
+
: "Run it yourself or attach a schedule", tone: nextScheduledRun ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Updated", value: formatRelativePast(automation.updatedAt, t), detail: formatDateTime(automation.updatedAt, { fallback: "—" }) })] }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1.12fr)_minmax(320px,0.88fr)]", children: [_jsxs("div", { className: "space-y-4", children: [_jsx(DetailSection, { title: "Prompt", children: _jsx("div", { className: "min-h-[10rem] px-4 py-4 text-sm leading-relaxed text-muted/85 whitespace-pre-wrap", children: task.description || "Describe what this task should do." }) }), _jsx(DetailSection, { title: automation.schedules.length > 0 ? "Starts when" : "How it starts", children: automation.schedules.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: automation.schedules.map((schedule) => (_jsx(OverviewListItem, { title: schedule.displayName, meta: scheduleLabel(schedule, t, uiLanguage), detail: formatDateTime(schedule.nextRunAtMs ?? null, {
|
|
1043
|
+
fallback: "No next run queued",
|
|
1044
|
+
locale: uiLanguage,
|
|
1045
|
+
}), trailing: schedule.enabled ? "Live" : "Paused", tone: schedule.enabled ? "success" : "muted" }, schedule.id))) })) : (_jsx("div", { className: "px-4 py-4 text-xs-tight text-muted/70", children: "This task is manual right now. Add a schedule if you want it to start on its own." })) })] }), _jsx("div", { className: "space-y-4", children: _jsx(DetailSection, { title: "Details", children: _jsx(DetailFactList, { items: [
|
|
1046
|
+
{
|
|
1047
|
+
label: "State",
|
|
1048
|
+
value: task.isCompleted ? "Completed" : "Open",
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
label: "Source",
|
|
1052
|
+
value: automation.system
|
|
1053
|
+
? "Agent-owned checklist item"
|
|
1054
|
+
: "Simple prompt task",
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
label: "Next run",
|
|
1058
|
+
value: nextScheduledRun
|
|
1059
|
+
? formatDateTime(nextScheduledRun, {
|
|
1060
|
+
fallback: "—",
|
|
1061
|
+
locale: uiLanguage,
|
|
1062
|
+
})
|
|
1063
|
+
: "Manual",
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
label: "Tags",
|
|
1067
|
+
value: task.tags.length > 0 ? (_jsx("div", { className: "flex flex-wrap justify-end gap-1", children: task.tags.map((tag) => (_jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] text-muted", children: tag }, tag))) })) : ("None"),
|
|
1068
|
+
},
|
|
1069
|
+
] }) }) })] })] }));
|
|
1006
1070
|
}
|
|
1007
1071
|
const AUTOMATION_DRAFT_EXAMPLES = [
|
|
1008
1072
|
{
|
|
@@ -1067,22 +1131,26 @@ function OverviewMetricCard({ label, value, detail, tone = "default", }) {
|
|
|
1067
1131
|
: "text-txt";
|
|
1068
1132
|
return (_jsxs("div", { className: "rounded-xl border border-border/25 bg-bg/35 px-3 py-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]", children: [_jsx("div", { className: "text-[10px] font-semibold uppercase tracking-[0.14em] text-muted/70", children: label }), _jsx("div", { className: `mt-2 text-lg font-semibold leading-none ${valueClass}`, children: value }), _jsx("div", { className: "mt-2 text-[11px] leading-snug text-muted/70", children: detail })] }));
|
|
1069
1133
|
}
|
|
1134
|
+
function OverviewListItem({ title, badge, meta, detail, trailing, tone = "muted", onClick, }) {
|
|
1135
|
+
const content = (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(StatusDot, { tone: tone, className: "mt-1 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium text-txt", children: title }), badge ? (_jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: badge })) : null, trailing ? (_jsx("span", { className: "ml-auto shrink-0 text-[11px] text-muted/70", children: trailing })) : null] }), meta ? (_jsx("div", { className: "mt-0.5 text-[11px] leading-snug text-muted/70", children: meta })) : null, detail ? (_jsx("div", { className: "mt-1 line-clamp-1 text-[11px] leading-snug text-muted/60", children: detail })) : null] })] }));
|
|
1136
|
+
if (!onClick) {
|
|
1137
|
+
return _jsx("div", { className: "px-3 py-2 text-xs-tight", children: content });
|
|
1138
|
+
}
|
|
1139
|
+
return (_jsx("button", { type: "button", onClick: onClick, className: "w-full px-3 py-2 text-left text-xs-tight transition-colors hover:bg-bg-muted/40", children: content }));
|
|
1140
|
+
}
|
|
1070
1141
|
function AutomationsDashboard({ items, onSelectItem, onCreateTask, onCreateWorkflow, onUseIdea, }) {
|
|
1071
1142
|
const { t, uiLanguage } = useAutomationsViewContext();
|
|
1072
1143
|
const now = Date.now();
|
|
1073
1144
|
const visibleItems = useMemo(() => items.filter((item) => !item.system), [items]);
|
|
1074
1145
|
const scheduledEntries = useMemo(() => collectScheduledAutomationEntries(visibleItems), [visibleItems]);
|
|
1075
1146
|
const draftItems = useMemo(() => sortAutomationsByUpdatedAtDesc(visibleItems.filter((item) => item.isDraft)).slice(0, 4), [visibleItems]);
|
|
1076
|
-
const taskHighlights = useMemo(() => sortAutomationsByUpdatedAtDesc(visibleItems.filter((item) =>
|
|
1077
|
-
const workflowHighlights = useMemo(() => sortAutomationsByUpdatedAtDesc(visibleItems.filter((item) => item.type === "n8n_workflow"
|
|
1078
|
-
const
|
|
1079
|
-
const taskCount = visibleItems.filter((item) => item.type === "automation_draft" || item.trigger != null || item.task).length;
|
|
1147
|
+
const taskHighlights = useMemo(() => sortAutomationsByUpdatedAtDesc(visibleItems.filter((item) => item.type !== "n8n_workflow")).slice(0, 6), [visibleItems]);
|
|
1148
|
+
const workflowHighlights = useMemo(() => sortAutomationsByUpdatedAtDesc(visibleItems.filter((item) => item.type === "n8n_workflow")).slice(0, 6), [visibleItems]);
|
|
1149
|
+
const taskCount = visibleItems.filter((item) => item.type !== "n8n_workflow").length;
|
|
1080
1150
|
const workflowCount = visibleItems.filter((item) => item.type === "n8n_workflow").length;
|
|
1081
1151
|
const activeCount = visibleItems.filter((item) => item.enabled && !item.isDraft).length;
|
|
1082
1152
|
const activeScheduleCount = scheduledEntries.filter(({ schedule }) => schedule.enabled).length;
|
|
1083
|
-
const failingCount = scheduledEntries.filter(({ schedule }) => toneForLastStatus(schedule.lastStatus) === "danger").length;
|
|
1084
1153
|
const draftCount = visibleItems.filter((item) => item.isDraft).length;
|
|
1085
|
-
const totalCount = visibleItems.filter((item) => !item.isDraft).length;
|
|
1086
1154
|
const upcoming = useMemo(() => scheduledEntries
|
|
1087
1155
|
.filter(({ schedule }) => schedule.enabled &&
|
|
1088
1156
|
typeof schedule.nextRunAtMs === "number" &&
|
|
@@ -1112,7 +1180,6 @@ function AutomationsDashboard({ items, onSelectItem, onCreateTask, onCreateWorkf
|
|
|
1112
1180
|
key,
|
|
1113
1181
|
item,
|
|
1114
1182
|
tone: "danger",
|
|
1115
|
-
statusLabel: "Failed",
|
|
1116
1183
|
title: getOverviewDisplayTitle(item),
|
|
1117
1184
|
groupLabel: getAutomationGroupLabel(item),
|
|
1118
1185
|
meta: scheduleLabel(schedule, t, uiLanguage),
|
|
@@ -1120,101 +1187,54 @@ function AutomationsDashboard({ items, onSelectItem, onCreateTask, onCreateWorkf
|
|
|
1120
1187
|
`Last run ${formatRelativePast(schedule.lastRunAtIso, t)}.`,
|
|
1121
1188
|
trailing: formatRelativePast(schedule.lastRunAtIso, t),
|
|
1122
1189
|
}));
|
|
1123
|
-
for (const item of draftItems) {
|
|
1124
|
-
next.push({
|
|
1125
|
-
key: `draft:${item.id}`,
|
|
1126
|
-
item,
|
|
1127
|
-
tone: "warning",
|
|
1128
|
-
statusLabel: "Draft",
|
|
1129
|
-
title: getOverviewDisplayTitle(item),
|
|
1130
|
-
groupLabel: getAutomationGroupLabel(item),
|
|
1131
|
-
meta: "Finish this in the sidebar agent.",
|
|
1132
|
-
detail: item.description.trim() ||
|
|
1133
|
-
"Describe the trigger, timing, or result you want so this becomes a real automation.",
|
|
1134
|
-
trailing: formatRelativePast(item.updatedAt, t),
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
1190
|
return next.slice(0, 6);
|
|
1138
|
-
}, [
|
|
1139
|
-
if (
|
|
1140
|
-
return (_jsxs("div", { className: "space-y-4 px-1 pt-4", children: [_jsx("section", { className: "overflow-hidden rounded-xl border border-border/25 bg-[radial-gradient(circle_at_top_left,rgba(34,211,238,0.14),transparent_38%),radial-gradient(circle_at_top_right,rgba(34,197,94,0.12),transparent_32%),rgba(255,255,255,0.02)]", children: _jsxs("div", { className: "space-y-3 px-4 py-4 sm:px-5", children: [_jsxs("div", { className: "inline-flex items-center gap-2 rounded-full border border-border/25 bg-bg/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted/70", children: [_jsx(LayoutDashboard, { className: "h-3 w-3", "aria-hidden": true }), "Overview"] }), _jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-lg font-semibold text-txt", children: "Build your first
|
|
1191
|
+
}, [failures, t, uiLanguage]);
|
|
1192
|
+
if (taskCount === 0 && workflowCount === 0) {
|
|
1193
|
+
return (_jsxs("div", { className: "space-y-4 px-1 pt-4", children: [_jsx("section", { className: "overflow-hidden rounded-xl border border-border/25 bg-[radial-gradient(circle_at_top_left,rgba(34,211,238,0.14),transparent_38%),radial-gradient(circle_at_top_right,rgba(34,197,94,0.12),transparent_32%),rgba(255,255,255,0.02)]", children: _jsxs("div", { className: "space-y-3 px-4 py-4 sm:px-5", children: [_jsxs("div", { className: "inline-flex items-center gap-2 rounded-full border border-border/25 bg-bg/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted/70", children: [_jsx(LayoutDashboard, { className: "h-3 w-3", "aria-hidden": true }), "Overview"] }), _jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-lg font-semibold text-txt", children: "Build your first automation" }), _jsx("p", { className: "text-xs-tight text-muted/80", children: "Workflows handle multi-step pipelines; tasks are simple prompts that run on a schedule or from an event." })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "default", size: "sm", onClick: onCreateTask, children: "New task" }), _jsx(Button, { variant: "outline", size: "sm", onClick: onCreateWorkflow, children: "New workflow" })] })] }) }), _jsxs("div", { className: "grid gap-3 xl:grid-cols-2", children: [_jsx(DetailSection, { title: "Task ideas", children: _jsx("div", { className: "p-2", children: _jsx(OverviewIdeaGrid, { ideas: taskIdeas, onSelect: (idea) => onUseIdea(idea) }) }) }), _jsx(DetailSection, { title: "Workflow ideas", children: _jsx("div", { className: "p-2", children: _jsx(OverviewIdeaGrid, { ideas: workflowIdeas, onSelect: (idea) => onUseIdea(idea) }) }) })] }), draftItems.length > 0 && (_jsx(DetailSection, { title: "Drafts in progress", children: _jsx("div", { className: "divide-y divide-border/20", children: draftItems.map((item) => (_jsx(OverviewListItem, { onClick: () => onSelectItem(item), title: getOverviewDisplayTitle(item), badge: "Draft", meta: formatRelativePast(item.updatedAt, t), detail: item.description.trim() ||
|
|
1194
|
+
"Open it and keep shaping it in the sidebar agent.", tone: "warning" }, item.id))) }) }))] }));
|
|
1141
1195
|
}
|
|
1142
|
-
return (_jsxs("div", { className: "space-y-4 px-1 pt-4", children: [
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
:
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
? `${activeScheduleCount} live`
|
|
1174
|
-
: "none live" }), children: upcoming.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: upcoming.map(({ key, item, schedule }) => (_jsxs("button", { type: "button", onClick: () => onSelectItem(item), className: "flex w-full items-start gap-2 px-3 py-2 text-left text-xs-tight hover:bg-bg-muted/40", children: [_jsx(Clock3, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted/60", "aria-hidden": true }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium text-txt", children: getOverviewDisplayTitle(item) }), _jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: getAutomationGroupLabel(item) }), _jsx("span", { className: "ml-auto text-muted tabular-nums", children: schedule.nextRunAtMs
|
|
1175
|
-
? formatRelativeFuture(schedule.nextRunAtMs, t)
|
|
1176
|
-
: "—" })] }), _jsx("div", { className: "mt-0.5 text-[11px] text-muted/70", children: scheduleLabel(schedule, t, uiLanguage) }), _jsx("div", { className: "mt-1 text-[11px] text-muted/60", children: formatDateTime(schedule.nextRunAtMs ?? null, {
|
|
1177
|
-
fallback: "—",
|
|
1178
|
-
locale: uiLanguage,
|
|
1179
|
-
}) })] })] }, key))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: scheduledEntries.length > 0
|
|
1180
|
-
? "Nothing is queued right now. Enabled schedules will appear here when they have a next run."
|
|
1181
|
-
: "No time-based schedules yet. Event-driven automations can still run without appearing in this list." })) }), _jsx(DetailSection, { title: "Recent runs", children: recent.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: recent.map(({ key, item, schedule }) => {
|
|
1182
|
-
const tone = toneForLastStatus(schedule.lastStatus);
|
|
1183
|
-
return (_jsxs("button", { type: "button", onClick: () => onSelectItem(item), className: "flex w-full items-start gap-2 px-3 py-2 text-left text-xs-tight hover:bg-bg-muted/40", children: [_jsx(StatusDot, { tone: tone, className: "mt-1 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium text-txt", children: getOverviewDisplayTitle(item) }), _jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: getAutomationGroupLabel(item) }), _jsx("span", { className: "ml-auto text-muted/70 tabular-nums", children: formatRelativePast(schedule.lastRunAtIso, t) })] }), _jsx("div", { className: "mt-0.5 text-[11px] text-muted/70", children: scheduleLabel(schedule, t, uiLanguage) }), _jsx("div", { className: "mt-1 text-[11px] text-muted/60", children: formatDateTime(schedule.lastRunAtIso, {
|
|
1184
|
-
fallback: "—",
|
|
1185
|
-
locale: uiLanguage,
|
|
1186
|
-
}) })] })] }, key));
|
|
1187
|
-
}) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: scheduledEntries.length > 0
|
|
1188
|
-
? "No runs have completed yet."
|
|
1189
|
-
: "Runs will show up here once a scheduled automation starts executing." })) })] }), _jsxs("div", { className: "space-y-4", children: [_jsx(DetailSection, { title: "Tasks", action: _jsx(Button, { variant: "ghost", size: "sm", className: "h-7 px-2 text-xs", onClick: onCreateTask, children: "New task" }), children: taskHighlights.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: taskHighlights.map((item) => (_jsxs("button", { type: "button", onClick: () => onSelectItem(item), className: "flex w-full items-start gap-2 px-3 py-2 text-left text-xs-tight hover:bg-bg-muted/40", children: [_jsx(StatusDot, { tone: item.enabled ? "success" : "muted", className: "mt-1 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium text-txt", children: getOverviewDisplayTitle(item) }), item.schedules.length > 0 ? (_jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: formatScheduleCount(item.schedules.length) })) : null, _jsx("span", { className: "ml-auto text-muted/70 tabular-nums", children: formatRelativePast(item.updatedAt, t) })] }), _jsx("div", { className: "mt-1 line-clamp-2 text-[11px] text-muted/70", children: item.description.trim() ||
|
|
1190
|
-
(item.schedules.length > 0
|
|
1191
|
-
? scheduleLabel(item.schedules[0], t, uiLanguage)
|
|
1192
|
-
: "Simple prompt automation.") })] })] }, item.id))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: "No real tasks yet. Start with a simple prompt automation here." })) }), _jsx(DetailSection, { title: "Workflows", action: _jsx(Button, { variant: "ghost", size: "sm", className: "h-7 px-2 text-xs", onClick: onCreateWorkflow, children: "New workflow" }), children: workflowHighlights.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: workflowHighlights.map((item) => (_jsxs("button", { type: "button", onClick: () => onSelectItem(item), className: "flex w-full items-start gap-2 px-3 py-2 text-left text-xs-tight hover:bg-bg-muted/40", children: [_jsx(StatusDot, { tone: item.enabled ? "success" : "muted", className: "mt-1 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium text-txt", children: getOverviewDisplayTitle(item) }), _jsxs("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: [getWorkflowNodeCount(item), " nodes"] }), item.schedules.length > 0 ? (_jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: formatScheduleCount(item.schedules.length) })) : null, _jsx("span", { className: "ml-auto text-muted/70 tabular-nums", children: formatRelativePast(item.updatedAt, t) })] }), _jsx("div", { className: "mt-1 line-clamp-2 text-[11px] text-muted/70", children: item.description.trim() ||
|
|
1193
|
-
(item.schedules.length > 0
|
|
1194
|
-
? scheduleLabel(item.schedules[0], t, uiLanguage)
|
|
1195
|
-
: "n8n workflow with no time-based schedule yet.") })] })] }, item.id))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: "No real workflows yet. Use a workflow when the automation needs multiple deterministic steps." })) }), _jsx(DetailSection, { title: "Recently changed", children: recentlyChanged.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: recentlyChanged.map((item) => (_jsxs("button", { type: "button", onClick: () => onSelectItem(item), className: "flex w-full items-start gap-2 px-3 py-2 text-left text-xs-tight hover:bg-bg-muted/40", children: [_jsx(StatusDot, { tone: item.isDraft
|
|
1196
|
-
? "warning"
|
|
1197
|
-
: item.enabled
|
|
1198
|
-
? "success"
|
|
1199
|
-
: "muted", className: "mt-1 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate font-medium text-txt", children: getOverviewDisplayTitle(item) }), _jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: item.isDraft
|
|
1200
|
-
? "Draft"
|
|
1201
|
-
: getAutomationGroupLabel(item) }), _jsx("span", { className: "ml-auto text-muted/70 tabular-nums", children: formatRelativePast(item.updatedAt, t) })] }), _jsx("div", { className: "mt-1 line-clamp-2 text-[11px] text-muted/70", children: item.description.trim() ||
|
|
1202
|
-
(item.type === "n8n_workflow"
|
|
1203
|
-
? `${getWorkflowNodeCount(item)} nodes`
|
|
1204
|
-
: item.schedules.length > 0
|
|
1205
|
-
? scheduleLabel(item.schedules[0], t, uiLanguage)
|
|
1206
|
-
: "Updated recently.") })] })] }, item.id))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: "Changes will appear here as automations are created or updated." })) })] })] })] }));
|
|
1196
|
+
return (_jsxs("div", { className: "space-y-4 px-1 pt-4", children: [_jsxs("section", { className: "grid gap-4 xl:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)]", children: [_jsxs("div", { className: "rounded-xl border border-border/25 bg-[radial-gradient(circle_at_top_left,rgba(34,197,94,0.1),transparent_34%),radial-gradient(circle_at_top_right,rgba(56,189,248,0.12),transparent_30%),rgba(255,255,255,0.02)] px-4 py-4 sm:px-5", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "inline-flex items-center gap-2 rounded-full border border-border/25 bg-bg/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted/70", children: [_jsx(LayoutDashboard, { className: "h-3 w-3", "aria-hidden": true }), "Overview"] }), _jsx("h2", { className: "text-lg font-semibold text-txt", children: "Automations" })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "default", size: "sm", onClick: onCreateTask, children: "New task" }), _jsx(Button, { variant: "outline", size: "sm", onClick: onCreateWorkflow, children: "New workflow" })] })] }), _jsxs("div", { className: "mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-4", children: [_jsx(OverviewMetricCard, { label: "Live", value: _jsx("span", { className: "tabular-nums", children: activeCount }), detail: activeCount > 0 ? "Ready to run" : "Nothing enabled", tone: activeCount > 0 ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Scheduled", value: _jsx("span", { className: "tabular-nums", children: activeScheduleCount }), detail: activeScheduleCount > 0
|
|
1197
|
+
? "Time-based runs live"
|
|
1198
|
+
: "No live schedule", tone: activeScheduleCount > 0 ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Attention", value: _jsx("span", { className: "tabular-nums", children: attentionEntries.length }), detail: attentionEntries.length > 0 ? "Failed runs" : "Nothing urgent", tone: attentionEntries.length > 0 ? "danger" : "ok" }), _jsx(OverviewMetricCard, { label: "Drafts", value: _jsx("span", { className: "tabular-nums", children: draftCount }), detail: draftCount > 0 ? "Still being shaped" : "Nothing in progress", tone: draftCount > 0 ? "warning" : "default" })] })] }), _jsx(DetailSection, { title: "Next up", className: "h-full", children: nextUpcoming?.schedule.nextRunAtMs ? (_jsxs("button", { type: "button", onClick: () => onSelectItem(nextUpcoming.item), className: "flex w-full flex-col gap-2 px-4 py-4 text-left transition-colors hover:bg-bg-muted/40", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StatusDot, { tone: "success", className: "shrink-0" }), _jsx("span", { className: "truncate text-sm font-semibold text-txt", children: getOverviewDisplayTitle(nextUpcoming.item) }), _jsx("span", { className: "rounded bg-bg/50 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.12em] text-muted/70", children: getAutomationGroupLabel(nextUpcoming.item) })] }), _jsx("div", { className: "pl-4 text-[11px] text-muted/70", children: scheduleLabel(nextUpcoming.schedule, t, uiLanguage) }), _jsxs("div", { className: "pl-4", children: [_jsx("div", { className: "text-lg font-semibold text-txt", children: formatRelativeFuture(nextUpcoming.schedule.nextRunAtMs, t) }), _jsx("div", { className: "mt-1 text-[11px] text-muted/70", children: formatDateTime(nextUpcoming.schedule.nextRunAtMs, {
|
|
1199
|
+
fallback: "—",
|
|
1200
|
+
locale: uiLanguage,
|
|
1201
|
+
}) })] })] })) : (_jsxs("div", { className: "flex h-full min-h-[11.75rem] flex-col justify-between gap-3 px-4 py-4", children: [_jsx("div", { className: "space-y-1", children: _jsx("div", { className: "text-sm font-medium text-txt", children: "None queued." }) }), draftItems[0] ? (_jsx(Button, { variant: "outline", size: "sm", className: "w-fit", onClick: () => onSelectItem(draftItems[0]), children: "Continue draft" })) : null] })) })] }), attentionEntries.length > 0 ? (_jsx(DetailSection, { title: "Needs attention", children: _jsx("div", { className: "divide-y divide-border/20", children: attentionEntries.map((entry) => (_jsx(OverviewListItem, { onClick: () => onSelectItem(entry.item), title: entry.title, badge: entry.groupLabel, meta: entry.meta, detail: entry.detail, trailing: entry.trailing, tone: entry.tone }, entry.key))) }) })) : null, _jsxs("div", { className: "grid gap-4 xl:grid-cols-2", children: [_jsx(DetailSection, { title: "Upcoming runs", className: "h-full", children: upcoming.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: upcoming.map(({ key, item, schedule }) => (_jsx(OverviewListItem, { onClick: () => onSelectItem(item), title: getOverviewDisplayTitle(item), badge: getAutomationGroupLabel(item), meta: scheduleLabel(schedule, t, uiLanguage), detail: formatDateTime(schedule.nextRunAtMs ?? null, {
|
|
1202
|
+
fallback: "—",
|
|
1203
|
+
locale: uiLanguage,
|
|
1204
|
+
}), trailing: schedule.nextRunAtMs
|
|
1205
|
+
? formatRelativeFuture(schedule.nextRunAtMs, t)
|
|
1206
|
+
: "—", tone: "success" }, key))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: scheduledEntries.length > 0 ? "None queued." : "No schedules." })) }), _jsx(DetailSection, { title: "Recent runs", className: "h-full", children: recent.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: recent.map(({ key, item, schedule }) => {
|
|
1207
|
+
const tone = toneForLastStatus(schedule.lastStatus);
|
|
1208
|
+
return (_jsx(OverviewListItem, { onClick: () => onSelectItem(item), title: getOverviewDisplayTitle(item), badge: getAutomationGroupLabel(item), meta: scheduleLabel(schedule, t, uiLanguage), detail: formatDateTime(schedule.lastRunAtIso, {
|
|
1209
|
+
fallback: "—",
|
|
1210
|
+
locale: uiLanguage,
|
|
1211
|
+
}), trailing: formatRelativePast(schedule.lastRunAtIso, t), tone: tone }, key));
|
|
1212
|
+
}) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: "No runs yet." })) })] }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-2", children: [_jsx(DetailSection, { title: "Tasks", className: "h-full", action: _jsx(Button, { variant: "ghost", size: "sm", className: "h-7 px-2 text-xs", onClick: onCreateTask, children: "New task" }), children: taskHighlights.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: taskHighlights.map((item) => (_jsx(OverviewListItem, { onClick: () => onSelectItem(item), title: getOverviewDisplayTitle(item), badge: item.isDraft
|
|
1213
|
+
? "Draft"
|
|
1214
|
+
: item.schedules.length > 0
|
|
1215
|
+
? formatScheduleCount(item.schedules.length)
|
|
1216
|
+
: undefined, meta: item.isDraft
|
|
1217
|
+
? "In progress"
|
|
1218
|
+
: item.schedules.length > 0
|
|
1219
|
+
? scheduleLabel(item.schedules[0], t, uiLanguage)
|
|
1220
|
+
: "Event or manual", trailing: formatRelativePast(item.updatedAt, t), tone: getAutomationStatusTone(item) }, item.id))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: "No tasks yet." })) }), _jsx(DetailSection, { title: "Workflows", className: "h-full", action: _jsx(Button, { variant: "ghost", size: "sm", className: "h-7 px-2 text-xs", onClick: onCreateWorkflow, children: "New workflow" }), children: workflowHighlights.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: workflowHighlights.map((item) => (_jsx(OverviewListItem, { onClick: () => onSelectItem(item), title: getOverviewDisplayTitle(item), badge: item.isDraft
|
|
1221
|
+
? "Draft"
|
|
1222
|
+
: `${getWorkflowNodeCount(item)} nodes`, meta: item.isDraft
|
|
1223
|
+
? "In progress"
|
|
1224
|
+
: item.schedules.length > 0
|
|
1225
|
+
? scheduleLabel(item.schedules[0], t, uiLanguage)
|
|
1226
|
+
: "Event or manual", trailing: formatRelativePast(item.updatedAt, t), tone: getAutomationStatusTone(item) }, item.id))) })) : (_jsx("div", { className: "px-3 py-4 text-xs-tight text-muted/70", children: "No workflows yet." })) })] })] }));
|
|
1207
1227
|
}
|
|
1208
|
-
function AutomationDraftPane({ automation, onSeedPrompt, }) {
|
|
1228
|
+
function AutomationDraftPane({ automation, onSeedPrompt, onDeleteDraft, }) {
|
|
1209
1229
|
const chatChrome = useAppWorkspaceChatChrome();
|
|
1210
1230
|
const openSidebarDraftChat = useCallback((prompt) => {
|
|
1211
1231
|
chatChrome?.openChat();
|
|
1212
1232
|
onSeedPrompt(prompt);
|
|
1213
1233
|
}, [chatChrome, onSeedPrompt]);
|
|
1214
|
-
return (_jsxs("div", { className: "space-y-4 px-4 pt-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-lg font-semibold text-txt", children: getAutomationDisplayTitle(automation) }), _jsx("p", { className: "max-w-2xl text-xs-tight text-muted/80", children: "Use the sidebar agent to turn this draft into a
|
|
1234
|
+
return (_jsxs("div", { className: "space-y-4 px-4 pt-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-lg font-semibold text-txt", children: getAutomationDisplayTitle(automation) }), _jsx("p", { className: "max-w-2xl text-xs-tight text-muted/80", children: "Use the sidebar agent to turn this draft into a task or workflow. Say what should start it and what result you want." })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 px-3 text-sm", onClick: () => openSidebarDraftChat(DESCRIBE_WORKFLOW_PROMPT), children: DESCRIBE_WORKFLOW_PROMPT }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-8 px-3 text-sm text-danger hover:bg-danger/10 hover:text-danger", onClick: () => void onDeleteDraft(automation), children: "Delete draft" })] })] }), _jsx("div", { className: "grid gap-1.5 sm:grid-cols-2", children: AUTOMATION_DRAFT_EXAMPLES.map((example) => {
|
|
1215
1235
|
const Icon = example.icon;
|
|
1216
1236
|
return (_jsxs("button", { type: "button", onClick: () => openSidebarDraftChat(example.prompt), className: "group flex items-start gap-2 rounded-[var(--radius-sm)] border border-border/25 bg-bg/30 px-3 py-2 text-left transition-colors hover:border-accent/40 hover:bg-accent/5", children: [_jsx(Icon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-accent/80", "aria-hidden": true }), _jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [_jsx("div", { className: "text-xs-tight font-semibold text-txt", children: example.label }), _jsx("div", { className: "line-clamp-2 text-[11px] leading-snug text-muted/70", children: example.prompt })] })] }, example.label));
|
|
1217
|
-
}) }), _jsx("p", { className: "px-1 text-[11px] text-muted/60", children: "
|
|
1237
|
+
}) }), _jsx("p", { className: "px-1 text-[11px] text-muted/60", children: "The draft updates here as the automation takes shape." })] }));
|
|
1218
1238
|
}
|
|
1219
1239
|
function IconAction({ icon, label, onClick, tone, disabled, ariaBusy, }) {
|
|
1220
1240
|
const toneClass = tone === "warning"
|
|
@@ -1235,11 +1255,11 @@ function DetailStatusIndicator({ label, tone, dotOnly = false, }) {
|
|
|
1235
1255
|
}
|
|
1236
1256
|
return _jsx(StatusBadge, { label: label, variant: tone, withDot: true });
|
|
1237
1257
|
}
|
|
1238
|
-
function
|
|
1239
|
-
return (
|
|
1258
|
+
function DetailSection({ title, action, children, className, }) {
|
|
1259
|
+
return (_jsxs("section", { className: `rounded-[var(--radius-sm)] border border-border/25 bg-bg/20 ${className ?? ""}`, children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border/20 px-3 py-1.5", children: [_jsx("div", { className: "text-2xs font-semibold uppercase tracking-[0.14em] text-muted", children: title }), action] }), _jsx("div", { className: "py-1", children: children })] }));
|
|
1240
1260
|
}
|
|
1241
|
-
function
|
|
1242
|
-
return (
|
|
1261
|
+
function DetailFactList({ items, }) {
|
|
1262
|
+
return (_jsx("dl", { className: "divide-y divide-border/20", children: items.map((item) => (_jsxs("div", { className: "flex items-start justify-between gap-3 px-3 py-2 text-xs-tight", children: [_jsx("dt", { className: "shrink-0 text-muted/70", children: item.label }), _jsx("dd", { className: "min-w-0 text-right text-txt", children: item.value })] }, item.label))) }));
|
|
1243
1263
|
}
|
|
1244
1264
|
function TriggerAutomationDetailPane({ automation, onPromoteToWorkflow, }) {
|
|
1245
1265
|
const { t, uiLanguage, openEditTrigger, onRunSelectedTrigger, onToggleTriggerEnabled, loadTriggerRuns, triggerRunsById, setForm, setEditorOpen, setEditingId, setSelectedItemId, setSelectedItemKind, } = useAutomationsViewContext();
|
|
@@ -1265,7 +1285,13 @@ function TriggerAutomationDetailPane({ automation, onPromoteToWorkflow, }) {
|
|
|
1265
1285
|
counts.failureCount += 1;
|
|
1266
1286
|
return counts;
|
|
1267
1287
|
}, { failureCount: 0, successCount: 0 });
|
|
1268
|
-
|
|
1288
|
+
const nextRunLabel = trigger.nextRunAtMs
|
|
1289
|
+
? formatRelativeFuture(trigger.nextRunAtMs, t)
|
|
1290
|
+
: "Event or manual";
|
|
1291
|
+
const whatRuns = trigger.kind === "workflow"
|
|
1292
|
+
? trigger.workflowName || "Selected workflow"
|
|
1293
|
+
: trigger.instructions || "No prompt yet.";
|
|
1294
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(DetailHeader, { icon: _jsx(Clock3, { className: "h-3.5 w-3.5", "aria-hidden": true }), title: getAutomationDisplayTitle(automation), description: automation.description, status: _jsx(DetailStatusIndicator, { label: trigger.enabled ? "Active" : "Paused", tone: trigger.enabled ? "success" : "muted", dotOnly: trigger.enabled }), actions: _jsxs(_Fragment, { children: [_jsx(IconAction, { label: trigger.enabled ? "Pause" : "Resume", onClick: () => void onToggleTriggerEnabled(trigger.id, trigger.enabled), icon: trigger.enabled ? (_jsx(Pause, { className: "h-3.5 w-3.5" })) : (_jsx(Play, { className: "h-3.5 w-3.5" })), tone: trigger.enabled ? "warning" : "ok" }), _jsx(IconAction, { label: t("triggersview.RunNow"), onClick: () => void onRunSelectedTrigger(trigger.id), icon: _jsx(Zap, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: t("triggersview.Edit"), onClick: () => openEditTrigger(trigger), icon: _jsx(EditIcon, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: t("heartbeatsview.duplicate"), onClick: () => {
|
|
1269
1295
|
setForm({
|
|
1270
1296
|
...formFromTrigger(trigger),
|
|
1271
1297
|
displayName: `${trigger.displayName} (copy)`,
|
|
@@ -1274,36 +1300,43 @@ function TriggerAutomationDetailPane({ automation, onPromoteToWorkflow, }) {
|
|
|
1274
1300
|
setEditingId(null);
|
|
1275
1301
|
setSelectedItemId(null);
|
|
1276
1302
|
setSelectedItemKind(null);
|
|
1277
|
-
}, icon: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: "Compile to Workflow", onClick: () => void onPromoteToWorkflow(automation), icon: _jsx(GitBranch, { className: "h-3.5 w-3.5" }) })] }) }), _jsx(
|
|
1278
|
-
|
|
1279
|
-
label: "Schedule",
|
|
1280
|
-
value: scheduleLabel(trigger, t, uiLanguage),
|
|
1281
|
-
},
|
|
1282
|
-
{
|
|
1283
|
-
label: "Last run",
|
|
1284
|
-
value: formatDateTime(trigger.lastRunAtIso, {
|
|
1285
|
-
fallback: "—",
|
|
1303
|
+
}, icon: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: "Compile to Workflow", onClick: () => void onPromoteToWorkflow(automation), icon: _jsx(GitBranch, { className: "h-3.5 w-3.5" }) })] }) }), _jsxs("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-5", children: [_jsx(OverviewMetricCard, { label: "Runs", value: _jsx("span", { className: "tabular-nums", children: selectedRuns.length }), detail: `${successCount} successful · ${failureCount} failed`, tone: failureCount > 0 ? "danger" : successCount > 0 ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Starts", value: getTriggerStartModeLabel(trigger), detail: scheduleLabel(trigger, t, uiLanguage) }), _jsx(OverviewMetricCard, { label: "When it fires", value: getTriggerWakeModeLabel(trigger), detail: trigger.enabled ? "Enabled" : "Paused", tone: trigger.enabled ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Next run", value: nextRunLabel, detail: formatDateTime(trigger.nextRunAtMs, {
|
|
1304
|
+
fallback: "No time-based run queued",
|
|
1286
1305
|
locale: uiLanguage,
|
|
1287
|
-
}),
|
|
1288
|
-
|
|
1289
|
-
{
|
|
1290
|
-
label: "Next run",
|
|
1291
|
-
value: formatDateTime(trigger.nextRunAtMs, {
|
|
1292
|
-
fallback: "—",
|
|
1306
|
+
}), tone: trigger.nextRunAtMs ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Last run", value: formatRelativePast(trigger.lastRunAtIso, t), detail: formatDateTime(trigger.lastRunAtIso, {
|
|
1307
|
+
fallback: "Not run yet",
|
|
1293
1308
|
locale: uiLanguage,
|
|
1294
|
-
}),
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1309
|
+
}) })] }), _jsx(DetailSection, { title: trigger.kind === "workflow" ? "Runs this workflow" : "Prompt", children: _jsx("div", { className: "min-h-[8rem] px-4 py-4 text-sm leading-relaxed text-muted/85 whitespace-pre-wrap", children: whatRuns }) }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(320px,0.95fr)]", children: [_jsx(DetailSection, { title: "Run history", className: "h-full", action: _jsx(IconAction, { label: t("common.refresh"), onClick: () => void loadTriggerRuns(trigger.id), icon: _jsx(RefreshCw, { className: "h-3.5 w-3.5" }) }), children: !hasLoadedRuns ? (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-xs-tight text-muted/70", children: [_jsx("div", { className: "h-3 w-3 animate-spin rounded-full border-2 border-muted/30 border-t-muted/80" }), t("databaseview.Loading")] })) : selectedRuns.length === 0 ? (_jsx("div", { className: "px-3 py-3 text-xs-tight text-muted/60", children: "No runs yet." })) : (_jsx("div", { className: "divide-y divide-border/20", children: selectedRuns.map((run) => (_jsxs("div", { className: "flex flex-wrap items-center gap-2 px-3 py-2 text-xs-tight", children: [_jsx(StatusBadge, { label: localizedExecutionStatus(run.status, t), variant: toneForLastStatus(run.status) }), _jsx("span", { className: "text-muted/70 tabular-nums", children: formatDateTime(run.startedAt, { locale: uiLanguage }) }), _jsx("span", { className: "text-muted/60", children: formatDurationMs(run.latencyMs, { t }) }), _jsx("span", { className: "ml-auto rounded bg-bg/40 px-1 py-0.5 font-mono text-[10px] text-muted/60", children: run.source }), run.error ? (_jsx("div", { className: "basis-full whitespace-pre-wrap rounded border border-danger/20 bg-danger/10 px-2 py-1 font-mono text-[11px] text-danger/90", children: run.error })) : null] }, run.triggerRunId))) })) }), _jsx(DetailSection, { title: "Schedule & behavior", className: "h-full", children: _jsx(DetailFactList, { items: [
|
|
1310
|
+
{
|
|
1311
|
+
label: "Schedule",
|
|
1312
|
+
value: scheduleLabel(trigger, t, uiLanguage),
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
label: "Trigger type",
|
|
1316
|
+
value: getTriggerStartModeLabel(trigger),
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
label: "Wake mode",
|
|
1320
|
+
value: getTriggerWakeModeLabel(trigger),
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
label: "Status",
|
|
1324
|
+
value: trigger.enabled ? "Enabled" : "Paused",
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
label: "Max runs",
|
|
1328
|
+
value: trigger.maxRuns ? String(trigger.maxRuns) : "Unlimited",
|
|
1329
|
+
},
|
|
1330
|
+
] }) })] })] }));
|
|
1301
1331
|
}
|
|
1302
|
-
function WorkflowAutomationDetailPane({ automation, n8nStatus, workflowFetchError, workflowBusyId, workflowOpsBusy, onDeleteWorkflow, onDuplicateWorkflow, onRefreshWorkflows, onStartLocalN8n, onToggleWorkflowActive, }) {
|
|
1332
|
+
function WorkflowAutomationDetailPane({ automation, n8nStatus, workflowFetchError, workflowBusyId, workflowOpsBusy, onDeleteDraft, onDeleteWorkflow, onDuplicateWorkflow, onGenerateWorkflow, onRefreshWorkflows, onScheduleWorkflow, onStartLocalN8n, onToggleWorkflowActive, }) {
|
|
1303
1333
|
const { t, uiLanguage } = useApp();
|
|
1304
1334
|
const chatChrome = useAppWorkspaceChatChrome();
|
|
1305
1335
|
const [fullWorkflow, setFullWorkflow] = useState(automation.workflow ?? null);
|
|
1306
1336
|
const [workflowLoading, setWorkflowLoading] = useState(false);
|
|
1337
|
+
const [workflowPrompt, setWorkflowPrompt] = useState("");
|
|
1338
|
+
const [workflowPromptError, setWorkflowPromptError] = useState(null);
|
|
1339
|
+
const [workflowPromptSaving, setWorkflowPromptSaving] = useState(false);
|
|
1307
1340
|
const workflowGenerating = useWorkflowGenerationState(automation.workflowId);
|
|
1308
1341
|
const busy = workflowOpsBusy ||
|
|
1309
1342
|
(automation.workflowId != null && workflowBusyId === automation.workflowId);
|
|
@@ -1312,10 +1345,42 @@ function WorkflowAutomationDetailPane({ automation, n8nStatus, workflowFetchErro
|
|
|
1312
1345
|
graphWorkflow?.nodes?.length ??
|
|
1313
1346
|
getWorkflowNodeCount(automation);
|
|
1314
1347
|
const workflowIsActive = graphWorkflow?.active ?? automation.enabled;
|
|
1348
|
+
const nextWorkflowRun = automation.schedules
|
|
1349
|
+
.map((schedule) => schedule.nextRunAtMs ?? 0)
|
|
1350
|
+
.filter((value) => value > 0)
|
|
1351
|
+
.sort((left, right) => left - right)[0];
|
|
1352
|
+
const workflowIdeas = AUTOMATION_DRAFT_EXAMPLES.filter((idea) => idea.kind === "workflow");
|
|
1353
|
+
const showWorkflowStarterIdeas = automation.isDraft || nodeCount === 0;
|
|
1354
|
+
const showWorkflowPromptBox = automation.isDraft || nodeCount === 0;
|
|
1315
1355
|
const handleDescribeWorkflow = useCallback(() => {
|
|
1316
1356
|
chatChrome?.openChat();
|
|
1317
1357
|
prefillPageChat(DESCRIBE_WORKFLOW_PROMPT, { select: true });
|
|
1318
1358
|
}, [chatChrome]);
|
|
1359
|
+
const handleUseWorkflowIdea = useCallback((idea) => {
|
|
1360
|
+
chatChrome?.openChat();
|
|
1361
|
+
prefillPageChat(idea.prompt, { select: true });
|
|
1362
|
+
}, [chatChrome]);
|
|
1363
|
+
const submitWorkflowPrompt = useCallback(async () => {
|
|
1364
|
+
const prompt = workflowPrompt.trim();
|
|
1365
|
+
if (!prompt) {
|
|
1366
|
+
setWorkflowPromptError("Describe what this workflow should do.");
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
setWorkflowPromptError(null);
|
|
1370
|
+
setWorkflowPromptSaving(true);
|
|
1371
|
+
try {
|
|
1372
|
+
await onGenerateWorkflow(automation, prompt);
|
|
1373
|
+
setWorkflowPrompt("");
|
|
1374
|
+
chatChrome?.openChat();
|
|
1375
|
+
prefillPageChat(prompt, { select: false });
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1378
|
+
setWorkflowPromptError(error instanceof Error ? error.message : "Failed to generate workflow.");
|
|
1379
|
+
}
|
|
1380
|
+
finally {
|
|
1381
|
+
setWorkflowPromptSaving(false);
|
|
1382
|
+
}
|
|
1383
|
+
}, [automation, chatChrome, onGenerateWorkflow, workflowPrompt]);
|
|
1319
1384
|
useEffect(() => {
|
|
1320
1385
|
let cancelled = false;
|
|
1321
1386
|
setFullWorkflow(automation.workflow ?? null);
|
|
@@ -1349,10 +1414,7 @@ function WorkflowAutomationDetailPane({ automation, n8nStatus, workflowFetchErro
|
|
|
1349
1414
|
automation.workflow,
|
|
1350
1415
|
automation.workflowId,
|
|
1351
1416
|
]);
|
|
1352
|
-
return (_jsxs("div", { className: "space-y-
|
|
1353
|
-
(automation.isDraft
|
|
1354
|
-
? "Draft the workflow in chat and the graph will fill in as it is created."
|
|
1355
|
-
: null), status: _jsx(DetailStatusIndicator, { label: automation.isDraft
|
|
1417
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(WorkflowRuntimeNotice, { status: n8nStatus, workflowFetchError: workflowFetchError, busy: busy, onRefresh: () => void onRefreshWorkflows(), onStartLocal: () => void onStartLocalN8n() }), _jsx(DetailHeader, { icon: _jsx(Workflow, { className: "h-3.5 w-3.5", "aria-hidden": true }), title: getAutomationDisplayTitle(automation), description: null, status: _jsx(DetailStatusIndicator, { label: automation.isDraft
|
|
1356
1418
|
? "Draft"
|
|
1357
1419
|
: automation.enabled
|
|
1358
1420
|
? "Active"
|
|
@@ -1360,7 +1422,7 @@ function WorkflowAutomationDetailPane({ automation, n8nStatus, workflowFetchErro
|
|
|
1360
1422
|
? "warning"
|
|
1361
1423
|
: automation.enabled
|
|
1362
1424
|
? "success"
|
|
1363
|
-
: "muted", dotOnly: !automation.isDraft && automation.enabled }), actions: automation.workflowId ? (_jsxs(_Fragment, { children: [_jsx(IconAction, { label: busy
|
|
1425
|
+
: "muted", dotOnly: !automation.isDraft && automation.enabled }), actions: automation.isDraft ? (_jsx(IconAction, { label: "Delete draft", onClick: () => void onDeleteDraft(automation), icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), tone: "danger" })) : automation.workflowId ? (_jsxs(_Fragment, { children: [_jsx(IconAction, { label: busy
|
|
1364
1426
|
? t("automations.n8n.updating", {
|
|
1365
1427
|
defaultValue: "Updating...",
|
|
1366
1428
|
})
|
|
@@ -1370,31 +1432,68 @@ function WorkflowAutomationDetailPane({ automation, n8nStatus, workflowFetchErro
|
|
|
1370
1432
|
})
|
|
1371
1433
|
: t("automations.n8n.activate", {
|
|
1372
1434
|
defaultValue: "Activate",
|
|
1373
|
-
}), onClick: () => void onToggleWorkflowActive(automation), disabled: busy, ariaBusy: busy, icon: workflowIsActive ? (_jsx(Pause, { className: "h-3.5 w-3.5" })) : (_jsx(Play, { className: "h-3.5 w-3.5" })), tone: workflowIsActive ? "warning" : "ok" }), _jsx(IconAction, { label: "Duplicate workflow", onClick: () => void onDuplicateWorkflow(automation), disabled: busy, icon: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: busy
|
|
1435
|
+
}), onClick: () => void onToggleWorkflowActive(automation), disabled: busy, ariaBusy: busy, icon: workflowIsActive ? (_jsx(Pause, { className: "h-3.5 w-3.5" })) : (_jsx(Play, { className: "h-3.5 w-3.5" })), tone: workflowIsActive ? "warning" : "ok" }), _jsx(IconAction, { label: "Schedule workflow", onClick: () => onScheduleWorkflow(automation), disabled: busy, icon: _jsx(Clock3, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: "Duplicate workflow", onClick: () => void onDuplicateWorkflow(automation), disabled: busy, icon: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx(IconAction, { label: busy
|
|
1374
1436
|
? t("automations.n8n.updating", {
|
|
1375
1437
|
defaultValue: "Updating...",
|
|
1376
1438
|
})
|
|
1377
1439
|
: t("automations.n8n.deleteWorkflow", {
|
|
1378
1440
|
defaultValue: "Delete workflow",
|
|
1379
|
-
}), onClick: () => void onDeleteWorkflow(automation), disabled: busy, ariaBusy: busy, icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), tone: "danger" })] })) : null }), _jsx(
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1441
|
+
}), onClick: () => void onDeleteWorkflow(automation), disabled: busy, ariaBusy: busy, icon: _jsx(Trash2, { className: "h-3.5 w-3.5" }), tone: "danger" })] })) : null }), _jsxs("div", { className: "grid gap-2 sm:grid-cols-2 xl:grid-cols-5", children: [_jsx(OverviewMetricCard, { label: "Status", value: automation.isDraft
|
|
1442
|
+
? "Draft"
|
|
1443
|
+
: workflowIsActive
|
|
1444
|
+
? "Active"
|
|
1445
|
+
: "Paused", detail: automation.schedules.length > 0
|
|
1446
|
+
? formatScheduleCount(automation.schedules.length)
|
|
1447
|
+
: "No schedule attached", tone: automation.isDraft ? "warning" : workflowIsActive ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Nodes", value: _jsx("span", { className: "tabular-nums", children: nodeCount }), detail: nodeCount > 0 ? "Visible in the graph below" : "Still blank" }), _jsx(OverviewMetricCard, { label: "Starts", value: automation.schedules.length > 0
|
|
1448
|
+
? formatScheduleCount(automation.schedules.length)
|
|
1449
|
+
: "Event or manual", detail: automation.schedules.length > 0
|
|
1450
|
+
? scheduleLabel(automation.schedules[0], t, uiLanguage)
|
|
1451
|
+
: "No time-based schedule yet" }), _jsx(OverviewMetricCard, { label: "Next run", value: nextWorkflowRun
|
|
1452
|
+
? formatRelativeFuture(nextWorkflowRun, t)
|
|
1453
|
+
: "Not queued", detail: nextWorkflowRun
|
|
1454
|
+
? formatDateTime(nextWorkflowRun, {
|
|
1455
|
+
fallback: "—",
|
|
1456
|
+
locale: uiLanguage,
|
|
1457
|
+
})
|
|
1458
|
+
: "Trigger it from an event or attach a schedule", tone: nextWorkflowRun ? "ok" : "default" }), _jsx(OverviewMetricCard, { label: "Updated", value: formatRelativePast(automation.updatedAt, t), detail: formatDateTime(automation.updatedAt, {
|
|
1389
1459
|
fallback: "—",
|
|
1390
1460
|
locale: uiLanguage,
|
|
1391
|
-
}),
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1461
|
+
}) })] }), showWorkflowPromptBox && (_jsx(DetailSection, { title: "Create workflow", children: _jsxs("div", { className: "p-3", children: [_jsxs("div", { className: "flex flex-col gap-2 rounded-full border border-border/30 bg-bg/50 p-1.5 shadow-sm sm:flex-row sm:items-center", children: [_jsx(Input, { "data-workflow-prompt-input": "true", value: workflowPrompt, onChange: (event) => setWorkflowPrompt(event.target.value), onKeyDown: (event) => {
|
|
1462
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
1463
|
+
event.preventDefault();
|
|
1464
|
+
void submitWorkflowPrompt();
|
|
1465
|
+
}
|
|
1466
|
+
}, placeholder: WORKFLOW_PROMPT_PLACEHOLDER, className: "min-h-10 flex-1 border-0 bg-transparent px-4 shadow-none focus-visible:ring-0", autoFocus: automation.isDraft }), _jsx(Button, { variant: "default", size: "sm", className: "h-10 shrink-0 rounded-full px-5", disabled: workflowPromptSaving || busy, onClick: () => void submitWorkflowPrompt(), children: workflowPromptSaving ? "Generating..." : "Generate" })] }), workflowPromptError && (_jsx("div", { className: "mt-2 text-xs text-danger", children: workflowPromptError }))] }) })), _jsx(DetailSection, { title: "Workflow graph", action: automation.isDraft || nodeCount === 0 ? (_jsx(Button, { variant: "ghost", size: "sm", className: "h-7 px-2 text-xs", onClick: () => {
|
|
1467
|
+
const input = document.querySelector("[data-workflow-prompt-input='true']");
|
|
1468
|
+
input?.focus();
|
|
1469
|
+
if (!input)
|
|
1470
|
+
handleDescribeWorkflow();
|
|
1471
|
+
}, children: DESCRIBE_WORKFLOW_PROMPT })) : undefined, children: _jsx("div", { className: "p-3", children: _jsx(WorkflowGraphViewer, { workflow: graphWorkflow, loading: workflowLoading, isGenerating: workflowGenerating, emptyStateActionLabel: DESCRIBE_WORKFLOW_PROMPT, emptyStateHelpText: "Describe it above to generate the graph.", onEmptyStateAction: () => {
|
|
1472
|
+
const input = document.querySelector("[data-workflow-prompt-input='true']");
|
|
1473
|
+
input?.focus();
|
|
1474
|
+
if (!input)
|
|
1475
|
+
handleDescribeWorkflow();
|
|
1476
|
+
}, status: n8nStatus }) }) }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(320px,0.95fr)]", children: [_jsx(DetailSection, { title: "Starts when", className: "h-full", children: automation.schedules.length > 0 ? (_jsx("div", { className: "divide-y divide-border/20", children: automation.schedules.map((schedule) => (_jsx(OverviewListItem, { title: schedule.displayName, meta: scheduleLabel(schedule, t, uiLanguage), detail: formatDateTime(schedule.nextRunAtMs ?? null, {
|
|
1477
|
+
fallback: "No next run queued",
|
|
1478
|
+
locale: uiLanguage,
|
|
1479
|
+
}), trailing: schedule.enabled ? "Live" : "Paused", tone: schedule.enabled ? "success" : "muted" }, schedule.id))) })) : (_jsx("div", { className: "px-4 py-4 text-xs-tight text-muted/70", children: "No schedule yet. Start it from an event or add one in the sidebar." })) }), showWorkflowStarterIdeas ? (_jsx(DetailSection, { title: "Starter ideas", className: "h-full", children: _jsx("div", { className: "p-2", children: _jsx(OverviewIdeaGrid, { ideas: workflowIdeas, onSelect: handleUseWorkflowIdea }) }) })) : (_jsx(DetailSection, { title: "Details", className: "h-full", children: _jsx(DetailFactList, { items: [
|
|
1480
|
+
{
|
|
1481
|
+
label: "Type",
|
|
1482
|
+
value: automation.isDraft ? "Draft" : "n8n workflow",
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
label: "Nodes",
|
|
1486
|
+
value: String(nodeCount),
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
label: "Schedules",
|
|
1490
|
+
value: String(automation.schedules.length),
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
label: "Updated",
|
|
1494
|
+
value: formatRelativePast(automation.updatedAt, t),
|
|
1495
|
+
},
|
|
1496
|
+
] }) }))] })] }));
|
|
1398
1497
|
}
|
|
1399
1498
|
function AutomationSidebarItem({ item, selected, onClick, onDoubleClick, }) {
|
|
1400
1499
|
let Icon = Zap;
|
|
@@ -1441,72 +1540,95 @@ function AutomationSidebarItem({ item, selected, onClick, onDoubleClick, }) {
|
|
|
1441
1540
|
function AutomationsSidebarChatGuideActions() {
|
|
1442
1541
|
return (_jsx(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 px-3 text-sm", onClick: () => prefillPageChat(DESCRIBE_WORKFLOW_PROMPT, { select: true }), children: DESCRIBE_WORKFLOW_PROMPT }));
|
|
1443
1542
|
}
|
|
1444
|
-
function AutomationsSidebarChat() {
|
|
1543
|
+
function AutomationsSidebarChat({ activeItem, }) {
|
|
1445
1544
|
const { activeConversationId, conversations, t } = useApp();
|
|
1446
|
-
const {
|
|
1545
|
+
const { refreshAutomations } = useAutomationsViewContext();
|
|
1546
|
+
const [overviewVisible, setOverviewVisible] = useState(false);
|
|
1547
|
+
const scopedActiveItem = overviewVisible ? null : activeItem;
|
|
1548
|
+
useEffect(() => {
|
|
1549
|
+
const overviewWindow = window;
|
|
1550
|
+
setOverviewVisible(Boolean(overviewWindow.__miladyAutomationsOverviewVisible));
|
|
1551
|
+
const handleOverviewVisibility = (event) => {
|
|
1552
|
+
const detail = event
|
|
1553
|
+
.detail;
|
|
1554
|
+
setOverviewVisible(Boolean(detail?.visible));
|
|
1555
|
+
};
|
|
1556
|
+
window.addEventListener(AUTOMATIONS_OVERVIEW_VISIBILITY_EVENT, handleOverviewVisibility);
|
|
1557
|
+
return () => window.removeEventListener(AUTOMATIONS_OVERVIEW_VISIBILITY_EVENT, handleOverviewVisibility);
|
|
1558
|
+
}, []);
|
|
1447
1559
|
const automationConversationAdapter = useMemo(() => {
|
|
1448
|
-
if (!
|
|
1560
|
+
if (!scopedActiveItem) {
|
|
1449
1561
|
return null;
|
|
1450
1562
|
}
|
|
1451
|
-
const bridgeConversationId = getAutomationBridgeIdForItem(
|
|
1452
|
-
if (
|
|
1453
|
-
const metadata =
|
|
1454
|
-
? buildWorkflowConversationMetadata(
|
|
1455
|
-
: buildWorkflowDraftConversationMetadata(
|
|
1563
|
+
const bridgeConversationId = getAutomationBridgeIdForItem(scopedActiveItem, activeConversationId, conversations);
|
|
1564
|
+
if (scopedActiveItem.type === "n8n_workflow") {
|
|
1565
|
+
const metadata = scopedActiveItem.workflowId
|
|
1566
|
+
? buildWorkflowConversationMetadata(scopedActiveItem.workflowId, scopedActiveItem.title, bridgeConversationId)
|
|
1567
|
+
: buildWorkflowDraftConversationMetadata(scopedActiveItem.draftId ?? scopedActiveItem.id, bridgeConversationId);
|
|
1456
1568
|
return {
|
|
1457
1569
|
allowClear: false,
|
|
1458
1570
|
buildRoutingMetadata: () => buildAutomationResponseRoutingMetadata(metadata),
|
|
1459
1571
|
identityKey: JSON.stringify({
|
|
1460
1572
|
metadata,
|
|
1461
|
-
title:
|
|
1573
|
+
title: scopedActiveItem.title,
|
|
1462
1574
|
}),
|
|
1463
1575
|
onAfterSend: () => void refreshAutomations(),
|
|
1464
1576
|
resolveConversation: () => resolveAutomationConversation({
|
|
1465
|
-
title:
|
|
1577
|
+
title: scopedActiveItem.title,
|
|
1466
1578
|
metadata,
|
|
1467
1579
|
}),
|
|
1468
1580
|
};
|
|
1469
1581
|
}
|
|
1470
|
-
if (
|
|
1471
|
-
const metadata = buildAutomationDraftConversationMetadata(
|
|
1582
|
+
if (scopedActiveItem.type === "automation_draft") {
|
|
1583
|
+
const metadata = buildAutomationDraftConversationMetadata(scopedActiveItem.draftId ?? scopedActiveItem.id, bridgeConversationId);
|
|
1472
1584
|
return {
|
|
1473
1585
|
allowClear: false,
|
|
1474
1586
|
buildRoutingMetadata: () => buildAutomationResponseRoutingMetadata(metadata),
|
|
1475
1587
|
identityKey: JSON.stringify({
|
|
1476
1588
|
metadata,
|
|
1477
|
-
title: getAutomationDisplayTitle(
|
|
1589
|
+
title: getAutomationDisplayTitle(scopedActiveItem),
|
|
1478
1590
|
}),
|
|
1479
1591
|
onAfterSend: () => void refreshAutomations(),
|
|
1480
1592
|
resolveConversation: () => resolveAutomationConversation({
|
|
1481
|
-
title: getAutomationDisplayTitle(
|
|
1593
|
+
title: getAutomationDisplayTitle(scopedActiveItem),
|
|
1482
1594
|
metadata,
|
|
1483
1595
|
}),
|
|
1484
1596
|
};
|
|
1485
1597
|
}
|
|
1486
1598
|
return null;
|
|
1487
1599
|
}, [
|
|
1600
|
+
scopedActiveItem,
|
|
1488
1601
|
activeConversationId,
|
|
1489
1602
|
conversations,
|
|
1490
1603
|
refreshAutomations,
|
|
1491
|
-
resolvedSelectedItem,
|
|
1492
1604
|
]);
|
|
1493
|
-
if (
|
|
1494
|
-
return (_jsx(PageScopedChatPane, { scope: "page-automations", conversationAdapter: automationConversationAdapter ?? undefined,
|
|
1605
|
+
if (scopedActiveItem?.type === "n8n_workflow") {
|
|
1606
|
+
return (_jsx(PageScopedChatPane, { scope: "page-automations", conversationAdapter: automationConversationAdapter ?? undefined, introOverride: {
|
|
1607
|
+
title: scopedActiveItem.isDraft ? "Workflow draft" : "Workflow",
|
|
1608
|
+
body: scopedActiveItem.isDraft
|
|
1609
|
+
? "Describe the trigger and steps. The graph updates here."
|
|
1610
|
+
: "Ask to inspect, change, or troubleshoot this workflow.",
|
|
1611
|
+
actions: scopedActiveItem.isDraft ? (_jsx(AutomationsSidebarChatGuideActions, {})) : undefined,
|
|
1612
|
+
}, placeholderOverride: scopedActiveItem.isDraft
|
|
1495
1613
|
? DESCRIBE_WORKFLOW_PROMPT
|
|
1496
1614
|
: t("automations.chat.placeholder"), systemAddendumOverride: WORKFLOW_SYSTEM_ADDENDUM }));
|
|
1497
1615
|
}
|
|
1498
|
-
if (
|
|
1499
|
-
return (_jsx(PageScopedChatPane, { scope: "page-automations", conversationAdapter: automationConversationAdapter ?? undefined,
|
|
1616
|
+
if (scopedActiveItem?.type === "automation_draft") {
|
|
1617
|
+
return (_jsx(PageScopedChatPane, { scope: "page-automations", conversationAdapter: automationConversationAdapter ?? undefined, introOverride: {
|
|
1618
|
+
title: "Draft",
|
|
1619
|
+
body: "Describe the result and what should start it.",
|
|
1620
|
+
}, placeholderOverride: "Describe your automation", systemAddendumOverride: AUTOMATION_DRAFT_SYSTEM_ADDENDUM }));
|
|
1500
1621
|
}
|
|
1501
1622
|
return (_jsx(PageScopedChatPane, { scope: "page-automations", placeholderOverride: "Describe your workflow, task, or schedule", introOverride: {
|
|
1623
|
+
title: "Automations",
|
|
1624
|
+
body: "Create or inspect a task or workflow.",
|
|
1502
1625
|
actions: _jsx(AutomationsSidebarChatGuideActions, {}),
|
|
1503
1626
|
} }));
|
|
1504
1627
|
}
|
|
1505
1628
|
function AutomationsLayout() {
|
|
1506
1629
|
const { activeConversationId, conversations } = useApp();
|
|
1507
|
-
const chatChrome = useAppWorkspaceChatChrome();
|
|
1508
1630
|
const ctx = useAutomationsViewContext();
|
|
1509
|
-
const { closeEditor, editorEnabled, editingId, editingTaskId, editorOpen, editorMode, form, formError, loadTriggerRuns, modalTitle, onDeleteTrigger, onRunSelectedTrigger, onSubmitTrigger, onToggleTriggerEnabled, openCreateTrigger, openCreateTask, saveFormAsTemplate, selectedItemId, setEditingId, setEditorOpen, setField, setFilter, setForm, setFormError, setSelectedItemId, setSelectedItemKind, showDetailPane, showFirstRunEmptyState, resolvedSelectedItem, t, templateNotice, triggers, filteredItems, triggerRunsById, triggersSaving, automationNodes, combinedError, isLoading, n8nStatus, workflowFetchError, } = ctx;
|
|
1631
|
+
const { closeEditor, editorEnabled, editingId, editingTaskId, editorOpen, editorMode, form, formError, loadTriggerRuns, modalTitle, onDeleteTrigger, onRunSelectedTrigger, onSubmitTrigger, onToggleTriggerEnabled, openCreateTrigger, openCreateTask, saveFormAsTemplate, selectedItemId, setEditingId, setEditorOpen, setEditorMode, setField, setFilter, setForm, setFormError, setSelectedItemId, setSelectedItemKind, showDetailPane, showFirstRunEmptyState, resolvedSelectedItem, t, templateNotice, triggers, filteredItems, triggerRunsById, triggersSaving, automationNodes, combinedError, isLoading, n8nStatus, workflowFetchError, } = ctx;
|
|
1510
1632
|
const [showDashboard, setShowDashboard] = useState(true);
|
|
1511
1633
|
const [collapsedSections, setCollapsedSections] = useState(() => new Set(["agent-owned"]));
|
|
1512
1634
|
const toggleSectionCollapsed = useCallback((key) => {
|
|
@@ -1527,6 +1649,19 @@ function AutomationsLayout() {
|
|
|
1527
1649
|
const [templatesModalOpen, setTemplatesModalOpen] = useState(false);
|
|
1528
1650
|
const [activeSubpage, setActiveSubpage] = useState(() => getAutomationSubpageFromPath(getNavigationPathFromWindow()));
|
|
1529
1651
|
const visibleItems = filteredItems;
|
|
1652
|
+
useEffect(() => {
|
|
1653
|
+
if (!showDashboard)
|
|
1654
|
+
return;
|
|
1655
|
+
setSelectedItemId(null);
|
|
1656
|
+
setSelectedItemKind(null);
|
|
1657
|
+
}, [setSelectedItemId, setSelectedItemKind, showDashboard]);
|
|
1658
|
+
useEffect(() => {
|
|
1659
|
+
window.__miladyAutomationsOverviewVisible =
|
|
1660
|
+
showDashboard;
|
|
1661
|
+
window.dispatchEvent(new CustomEvent(AUTOMATIONS_OVERVIEW_VISIBILITY_EVENT, {
|
|
1662
|
+
detail: { visible: showDashboard },
|
|
1663
|
+
}));
|
|
1664
|
+
}, [showDashboard]);
|
|
1530
1665
|
const syncSubpageFromLocation = useCallback(() => {
|
|
1531
1666
|
const pathname = getNavigationPathFromWindow();
|
|
1532
1667
|
const nextSubpage = getAutomationSubpageFromPath(pathname);
|
|
@@ -1707,10 +1842,54 @@ function AutomationsLayout() {
|
|
|
1707
1842
|
}
|
|
1708
1843
|
return data;
|
|
1709
1844
|
}, [ctx, selectedItemId, setSelectedItemId, setSelectedItemKind]);
|
|
1845
|
+
const bindConversationToWorkflow = useCallback(async (conversation, workflow, bridgeConversationId) => {
|
|
1846
|
+
const reboundMetadata = buildWorkflowConversationMetadata(workflow.id, workflow.name, bridgeConversationId ?? undefined);
|
|
1847
|
+
const { conversation: reboundConversation } = await client.updateConversation(conversation.id, {
|
|
1848
|
+
title: workflow.name,
|
|
1849
|
+
metadata: reboundMetadata,
|
|
1850
|
+
});
|
|
1851
|
+
setActiveWorkflowConversation(reboundConversation);
|
|
1852
|
+
return reboundConversation;
|
|
1853
|
+
}, []);
|
|
1854
|
+
const selectWorkflowById = useCallback((workflowId) => {
|
|
1855
|
+
setShowDashboard(false);
|
|
1856
|
+
setSelectedItemId(`workflow:${workflowId}`);
|
|
1857
|
+
setSelectedItemKind("workflow");
|
|
1858
|
+
setEditorOpen(false);
|
|
1859
|
+
setEditingId(null);
|
|
1860
|
+
ctx.setEditingTaskId(null);
|
|
1861
|
+
}, [ctx, setEditingId, setEditorOpen, setSelectedItemId, setSelectedItemKind]);
|
|
1862
|
+
const generateWorkflowFromPrompt = useCallback(async ({ prompt, title, conversation, bridgeConversationId, workflowId, }) => {
|
|
1863
|
+
setWorkflowOpsBusy(true);
|
|
1864
|
+
setPageNotice(null);
|
|
1865
|
+
try {
|
|
1866
|
+
const workflow = await client.generateN8nWorkflow({
|
|
1867
|
+
prompt,
|
|
1868
|
+
...(title?.trim() ? { name: title.trim() } : {}),
|
|
1869
|
+
...(workflowId ? { workflowId } : {}),
|
|
1870
|
+
});
|
|
1871
|
+
if (conversation) {
|
|
1872
|
+
await bindConversationToWorkflow(conversation, workflow, bridgeConversationId);
|
|
1873
|
+
}
|
|
1874
|
+
await ctx.refreshAutomations();
|
|
1875
|
+
selectWorkflowById(workflow.id);
|
|
1876
|
+
return workflow;
|
|
1877
|
+
}
|
|
1878
|
+
finally {
|
|
1879
|
+
setWorkflowOpsBusy(false);
|
|
1880
|
+
}
|
|
1881
|
+
}, [bindConversationToWorkflow, ctx, selectWorkflowById]);
|
|
1710
1882
|
const createWorkflowDraft = useCallback(async (options) => {
|
|
1711
1883
|
setPageNotice(null);
|
|
1712
1884
|
showAutomationsList();
|
|
1713
1885
|
const draftId = createWorkflowDraftId();
|
|
1886
|
+
setShowDashboard(false);
|
|
1887
|
+
setFilter("all");
|
|
1888
|
+
setSelectedItemId(`workflow-draft:${draftId}`);
|
|
1889
|
+
setSelectedItemKind("workflow");
|
|
1890
|
+
setEditorOpen(false);
|
|
1891
|
+
setEditingId(null);
|
|
1892
|
+
ctx.setEditingTaskId(null);
|
|
1714
1893
|
const bridgeConversationId = getAutomationBridgeIdForItem(resolvedSelectedItem, activeConversationId, conversations);
|
|
1715
1894
|
const metadata = buildWorkflowDraftConversationMetadata(draftId, bridgeConversationId);
|
|
1716
1895
|
try {
|
|
@@ -1720,25 +1899,18 @@ function AutomationsLayout() {
|
|
|
1720
1899
|
});
|
|
1721
1900
|
setActiveWorkflowConversation(conversation);
|
|
1722
1901
|
if (options?.initialPrompt?.trim()) {
|
|
1723
|
-
await
|
|
1902
|
+
await generateWorkflowFromPrompt({
|
|
1903
|
+
prompt: options.initialPrompt.trim(),
|
|
1904
|
+
title: options.title?.trim() || undefined,
|
|
1905
|
+
conversation,
|
|
1906
|
+
bridgeConversationId,
|
|
1907
|
+
});
|
|
1908
|
+
return;
|
|
1724
1909
|
}
|
|
1725
|
-
const data =
|
|
1726
|
-
? await refreshAutomationsWithDraftBinding(conversation)
|
|
1727
|
-
: await ctx.refreshAutomations();
|
|
1910
|
+
const data = await ctx.refreshAutomations();
|
|
1728
1911
|
const resolvedItem = findAutomationForConversation(data, conversation.id);
|
|
1729
|
-
setShowDashboard(false);
|
|
1730
|
-
setFilter("all");
|
|
1731
1912
|
setSelectedItemId(resolvedItem?.id ?? `workflow-draft:${draftId}`);
|
|
1732
1913
|
setSelectedItemKind("workflow");
|
|
1733
|
-
setEditorOpen(false);
|
|
1734
|
-
setEditingId(null);
|
|
1735
|
-
ctx.setEditingTaskId(null);
|
|
1736
|
-
if (!options?.initialPrompt?.trim()) {
|
|
1737
|
-
window.requestAnimationFrame(() => {
|
|
1738
|
-
chatChrome?.openChat();
|
|
1739
|
-
prefillPageChat(DESCRIBE_WORKFLOW_PROMPT, { select: true });
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
1914
|
}
|
|
1743
1915
|
catch (error) {
|
|
1744
1916
|
setPageNotice(error instanceof Error
|
|
@@ -1750,15 +1922,14 @@ function AutomationsLayout() {
|
|
|
1750
1922
|
conversations,
|
|
1751
1923
|
ctx,
|
|
1752
1924
|
findAutomationForConversation,
|
|
1753
|
-
|
|
1925
|
+
generateWorkflowFromPrompt,
|
|
1754
1926
|
resolvedSelectedItem,
|
|
1927
|
+
setFilter,
|
|
1755
1928
|
setEditingId,
|
|
1756
1929
|
setEditorOpen,
|
|
1757
|
-
setFilter,
|
|
1758
1930
|
setSelectedItemId,
|
|
1759
1931
|
setSelectedItemKind,
|
|
1760
1932
|
showAutomationsList,
|
|
1761
|
-
chatChrome,
|
|
1762
1933
|
]);
|
|
1763
1934
|
const promoteAutomationToWorkflow = useCallback(async (item) => {
|
|
1764
1935
|
await createWorkflowDraft({
|
|
@@ -1780,7 +1951,7 @@ function AutomationsLayout() {
|
|
|
1780
1951
|
showAutomationsList();
|
|
1781
1952
|
openCreateTask();
|
|
1782
1953
|
}, [openCreateTask, showAutomationsList]);
|
|
1783
|
-
const
|
|
1954
|
+
const openSeededTask = useCallback((idea) => {
|
|
1784
1955
|
showAutomationsList();
|
|
1785
1956
|
openCreateTrigger();
|
|
1786
1957
|
setForm({
|
|
@@ -1844,6 +2015,51 @@ function AutomationsLayout() {
|
|
|
1844
2015
|
setWorkflowBusyId(null);
|
|
1845
2016
|
}
|
|
1846
2017
|
}, [ctx, t]);
|
|
2018
|
+
const handleGenerateWorkflow = useCallback(async (item, prompt) => {
|
|
2019
|
+
const conversationId = item.room?.conversationId;
|
|
2020
|
+
const conversation = conversationId
|
|
2021
|
+
? (conversations.find((candidate) => candidate.id === conversationId) ??
|
|
2022
|
+
null)
|
|
2023
|
+
: null;
|
|
2024
|
+
const title = item.title.trim() && item.title !== WORKFLOW_DRAFT_TITLE
|
|
2025
|
+
? item.title
|
|
2026
|
+
: undefined;
|
|
2027
|
+
await generateWorkflowFromPrompt({
|
|
2028
|
+
prompt,
|
|
2029
|
+
title,
|
|
2030
|
+
conversation,
|
|
2031
|
+
bridgeConversationId: item.room?.terminalBridgeConversationId,
|
|
2032
|
+
workflowId: item.hasBackingWorkflow ? item.workflowId : null,
|
|
2033
|
+
});
|
|
2034
|
+
}, [conversations, generateWorkflowFromPrompt]);
|
|
2035
|
+
const handleScheduleWorkflow = useCallback((item) => {
|
|
2036
|
+
if (!item.workflowId) {
|
|
2037
|
+
setPageNotice("Generate the workflow before scheduling it.");
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
setForm({
|
|
2041
|
+
...emptyForm,
|
|
2042
|
+
displayName: `Run ${item.title}`,
|
|
2043
|
+
kind: "workflow",
|
|
2044
|
+
workflowId: item.workflowId,
|
|
2045
|
+
workflowName: item.title,
|
|
2046
|
+
});
|
|
2047
|
+
setEditorMode("trigger");
|
|
2048
|
+
setEditingId(null);
|
|
2049
|
+
ctx.setEditingTaskId(null);
|
|
2050
|
+
setSelectedItemId(null);
|
|
2051
|
+
setSelectedItemKind(null);
|
|
2052
|
+
setEditorOpen(true);
|
|
2053
|
+
setShowDashboard(false);
|
|
2054
|
+
}, [
|
|
2055
|
+
ctx,
|
|
2056
|
+
setEditingId,
|
|
2057
|
+
setEditorMode,
|
|
2058
|
+
setEditorOpen,
|
|
2059
|
+
setForm,
|
|
2060
|
+
setSelectedItemId,
|
|
2061
|
+
setSelectedItemKind,
|
|
2062
|
+
]);
|
|
1847
2063
|
const handleDeleteWorkflow = useCallback(async (item) => {
|
|
1848
2064
|
if (!item.workflowId) {
|
|
1849
2065
|
return;
|
|
@@ -1852,10 +2068,12 @@ function AutomationsLayout() {
|
|
|
1852
2068
|
title: t("automations.n8n.deleteWorkflow", {
|
|
1853
2069
|
defaultValue: "Delete workflow",
|
|
1854
2070
|
}),
|
|
1855
|
-
message:
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2071
|
+
message: item.schedules.length > 0
|
|
2072
|
+
? `Delete "${item.title}" and ${item.schedules.length} attached schedule${item.schedules.length === 1 ? "" : "s"}? This cannot be undone.`
|
|
2073
|
+
: t("automations.n8n.deleteConfirmWorkflow", {
|
|
2074
|
+
defaultValue: 'Delete "{{name}}"? This cannot be undone.',
|
|
2075
|
+
name: item.title,
|
|
2076
|
+
}),
|
|
1859
2077
|
confirmLabel: t("automations.n8n.deleteWorkflow", {
|
|
1860
2078
|
defaultValue: "Delete workflow",
|
|
1861
2079
|
}),
|
|
@@ -1868,6 +2086,7 @@ function AutomationsLayout() {
|
|
|
1868
2086
|
setPageNotice(null);
|
|
1869
2087
|
try {
|
|
1870
2088
|
await client.deleteN8nWorkflow(item.workflowId);
|
|
2089
|
+
await Promise.all(item.schedules.map((schedule) => client.deleteTrigger(schedule.id)));
|
|
1871
2090
|
await ctx.refreshAutomations();
|
|
1872
2091
|
}
|
|
1873
2092
|
catch (error) {
|
|
@@ -1888,24 +2107,57 @@ function AutomationsLayout() {
|
|
|
1888
2107
|
setPageNotice(null);
|
|
1889
2108
|
try {
|
|
1890
2109
|
const workflow = await client.getN8nWorkflow(item.workflowId);
|
|
1891
|
-
await
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
...item,
|
|
1895
|
-
workflow,
|
|
1896
|
-
}),
|
|
1897
|
-
});
|
|
2110
|
+
const copy = await client.createN8nWorkflow(buildWorkflowCopyRequest(workflow, `${item.title} Copy`));
|
|
2111
|
+
await ctx.refreshAutomations();
|
|
2112
|
+
selectWorkflowById(copy.id);
|
|
1898
2113
|
}
|
|
1899
2114
|
catch (error) {
|
|
1900
2115
|
setPageNotice(error instanceof Error
|
|
1901
2116
|
? error.message
|
|
1902
2117
|
: "Failed to duplicate workflow.");
|
|
1903
2118
|
}
|
|
1904
|
-
}, [
|
|
2119
|
+
}, [ctx, selectWorkflowById]);
|
|
2120
|
+
const handleDeleteDraft = useCallback(async (item) => {
|
|
2121
|
+
const conversationId = item.room?.conversationId;
|
|
2122
|
+
if (!conversationId) {
|
|
2123
|
+
setPageNotice("This draft is missing its automation room.");
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
const confirmed = await confirmDesktopAction({
|
|
2127
|
+
title: "Delete draft",
|
|
2128
|
+
message: `Delete "${getAutomationDisplayTitle(item)}"? This removes the draft room and its conversation.`,
|
|
2129
|
+
confirmLabel: "Delete draft",
|
|
2130
|
+
cancelLabel: t("common.cancel"),
|
|
2131
|
+
type: "warning",
|
|
2132
|
+
});
|
|
2133
|
+
if (!confirmed)
|
|
2134
|
+
return;
|
|
2135
|
+
setPageNotice(null);
|
|
2136
|
+
try {
|
|
2137
|
+
await client.deleteConversation(conversationId);
|
|
2138
|
+
if (selectedItemId === item.id) {
|
|
2139
|
+
setSelectedItemId(null);
|
|
2140
|
+
setSelectedItemKind(null);
|
|
2141
|
+
}
|
|
2142
|
+
if (activeWorkflowConversation?.id === conversationId) {
|
|
2143
|
+
setActiveWorkflowConversation(null);
|
|
2144
|
+
}
|
|
2145
|
+
await ctx.refreshAutomations();
|
|
2146
|
+
setShowDashboard(true);
|
|
2147
|
+
}
|
|
2148
|
+
catch (error) {
|
|
2149
|
+
setPageNotice(error instanceof Error ? error.message : "Failed to delete draft.");
|
|
2150
|
+
}
|
|
2151
|
+
}, [
|
|
2152
|
+
activeWorkflowConversation?.id,
|
|
2153
|
+
ctx,
|
|
2154
|
+
selectedItemId,
|
|
2155
|
+
setSelectedItemId,
|
|
2156
|
+
setSelectedItemKind,
|
|
2157
|
+
t,
|
|
2158
|
+
]);
|
|
1905
2159
|
const workflowItems = useMemo(() => visibleItems.filter((item) => item.type === "n8n_workflow"), [visibleItems]);
|
|
1906
|
-
const taskItems = useMemo(() => visibleItems.filter((item) => item.type === "automation_draft" ||
|
|
1907
|
-
item.trigger != null ||
|
|
1908
|
-
(item.task != null && !item.system)), [visibleItems]);
|
|
2160
|
+
const taskItems = useMemo(() => visibleItems.filter((item) => item.type === "automation_draft" || item.trigger != null), [visibleItems]);
|
|
1909
2161
|
const agentOwnedItems = useMemo(() => visibleItems.filter((item) => item.task != null && item.system), [visibleItems]);
|
|
1910
2162
|
// Watch active drafts: while any unbound draft exists, poll the
|
|
1911
2163
|
// automations list and try to rebind it to whatever the agent
|
|
@@ -1954,7 +2206,7 @@ function AutomationsLayout() {
|
|
|
1954
2206
|
defaultValue: "Expand automations",
|
|
1955
2207
|
}), bottomAction: _jsxs("button", { type: "button", onClick: () => showNodeCatalog(), "aria-pressed": nodeCatalogActive, title: nodeCatalogLabel, className: `inline-flex h-6 shrink-0 items-center gap-1.5 rounded-[var(--radius-sm)] bg-transparent px-1.5 text-2xs font-semibold uppercase tracking-[0.12em] transition-colors ${nodeCatalogActive ? "text-txt" : "text-muted hover:text-txt"}`, children: [_jsx(Grid3x3, { className: "h-3.5 w-3.5", "aria-hidden": true }), _jsx("span", { children: nodeCatalogLabel })] }), collapsedRailAction: _jsx(SidebarCollapsedActionButton, { "aria-label": "Create task or workflow", onClick: () => setCreateDialogMode("all"), children: _jsx(Plus, { className: "h-4 w-4" }) }), collapsedRailItems: visibleItems.map((item) => (_jsx(SidebarContent.RailItem, { "aria-label": getAutomationDisplayTitle(item), title: getAutomationDisplayTitle(item), active: item.id === selectedItemId, indicatorTone: getAutomationIndicatorTone(item), onClick: () => selectItem(item), children: railMonogram(getAutomationDisplayTitle(item)) }, item.id))), children: _jsx(SidebarScrollRegion, { className: "px-1 pb-2 pt-0", children: _jsxs(SidebarPanel, { className: "bg-transparent gap-0 p-0 shadow-none", children: [isLoading && (_jsxs("div", { className: "flex items-center gap-2 px-2 py-1.5 text-2xs text-muted", children: [_jsx("div", { className: "h-3 w-3 animate-spin rounded-full border-2 border-muted/30 border-t-muted/80" }), t("common.loading")] })), _jsxs("button", { type: "button", onClick: showOverview, "aria-current": showDashboard ? "page" : undefined, className: `mt-0.5 flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1 text-left text-xs-tight transition-colors ${showDashboard
|
|
1956
2208
|
? "bg-accent/15 text-txt"
|
|
1957
|
-
: "text-txt hover:bg-bg-muted/50"}`, children: [_jsx(LayoutDashboard, { className: "h-3.5 w-3.5 shrink-0 text-muted/70", "aria-hidden": true }), _jsx("span", { className: "truncate", children: "Overview" })] }), _jsxs("div", { className: "mt-0.5 space-y-1", children: [_jsx(AutomationCollapsibleSection, { sectionKey: "tasks", label: "Tasks", icon: _jsx(FileText, { className: "h-3.5 w-3.5", "aria-hidden": true }), count: taskItems.length, collapsed: collapsedSections.has("tasks"), onToggleCollapsed: toggleSectionCollapsed, onAdd:
|
|
2209
|
+
: "text-txt hover:bg-bg-muted/50"}`, children: [_jsx(LayoutDashboard, { className: "h-3.5 w-3.5 shrink-0 text-muted/70", "aria-hidden": true }), _jsx("span", { className: "truncate", children: "Overview" })] }), _jsxs("div", { className: "mt-0.5 space-y-1", children: [_jsx(AutomationCollapsibleSection, { sectionKey: "tasks", label: "Tasks", icon: _jsx(FileText, { className: "h-3.5 w-3.5", "aria-hidden": true }), count: taskItems.length, collapsed: collapsedSections.has("tasks"), onToggleCollapsed: toggleSectionCollapsed, onAdd: handleZeroStateNewTask, addLabel: "Create task", emptyLabel: "No tasks", children: taskItems.map(renderItem) }), _jsx(AutomationCollapsibleSection, { sectionKey: "workflows", label: "Workflows", icon: _jsx(Workflow, { className: "h-3.5 w-3.5", "aria-hidden": true }), count: workflowItems.length, collapsed: collapsedSections.has("workflows"), onToggleCollapsed: toggleSectionCollapsed, onAdd: () => void createWorkflowDraft(), addLabel: "Create workflow", emptyLabel: "No workflows", children: workflowItems.map(renderItem) }), _jsx(AutomationCollapsibleSection, { sectionKey: "agent-owned", label: "Agent Owned", icon: _jsx(SquareTerminal, { className: "h-3.5 w-3.5", "aria-hidden": true }), count: agentOwnedItems.length, collapsed: collapsedSections.has("agent-owned"), onToggleCollapsed: toggleSectionCollapsed, emptyLabel: "No agent-owned automations", children: agentOwnedItems.map(renderItem) })] })] }) }) }));
|
|
1958
2210
|
return (_jsxs(PageLayout, { className: "h-full bg-transparent", "data-testid": "automations-shell", sidebar: automationsSidebar, contentInnerClassName: "w-full", footer: _jsx(WidgetHost, { slot: "automations", className: "py-2" }), mobileSidebarLabel: mobileSidebarLabel, children: [_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [activeSubpage === "node-catalog" ||
|
|
1959
2211
|
(!showDashboard && showDetailPane) ? (_jsx("button", { type: "button", className: "mb-3 flex items-center gap-2 rounded-2xl border border-border/30 bg-bg/25 px-4 py-3 text-base font-medium text-muted hover:text-txt md:hidden", onClick: () => {
|
|
1960
2212
|
if (activeSubpage === "node-catalog") {
|
|
@@ -1966,7 +2218,7 @@ function AutomationsLayout() {
|
|
|
1966
2218
|
setEditorOpen(false);
|
|
1967
2219
|
setEditingId(null);
|
|
1968
2220
|
ctx.setEditingTaskId(null);
|
|
1969
|
-
}, children: "\u2190 Back" })) : null, (pageNotice || combinedError) && (_jsx(PagePanel, { variant: "padded", className: "mb-4 border border-danger/20 bg-danger/5", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("p", { className: "text-sm text-danger", children: pageNotice ?? combinedError }), pageNotice && (_jsx(Button, { variant: "ghost", size: "sm", className: "text-danger hover:bg-danger/10", onClick: () => setPageNotice(null), children: "Dismiss" }))] }) })), editorOpen || editingId || editingTaskId ? (editorMode === "task" || editingTaskId ? (_jsx(TaskForm, {})) : (_jsx(HeartbeatForm, { form: form, editingId: editingId, editorEnabled: editorEnabled, modalTitle: modalTitle, formError: formError, triggersSaving: triggersSaving, templateNotice: templateNotice, triggers: triggers, triggerRunsById: triggerRunsById, t: t, selectedTriggerId: editingId, setField: setField, setForm: setForm, setFormError: setFormError, closeEditor: closeEditor, onSubmit: onSubmitTrigger, onDelete: onDeleteTrigger, onRunSelectedTrigger: onRunSelectedTrigger, onToggleTriggerEnabled: onToggleTriggerEnabled, saveFormAsTemplate: saveFormAsTemplate, loadTriggerRuns: loadTriggerRuns, kickerLabelCreate: "New task", kickerLabelEdit: "Edit task", submitLabelCreate: "Create task", submitLabelEdit: "Save task" }))) : activeSubpage === "node-catalog" ? (_jsx(AutomationNodeCatalogPane, { nodes: automationNodes })) : showDashboard ? (_jsx(AutomationsDashboard, { items: ctx.allItems, onSelectItem: selectItem, onCreateTask:
|
|
2221
|
+
}, children: "\u2190 Back" })) : null, (pageNotice || combinedError) && (_jsx(PagePanel, { variant: "padded", className: "mb-4 border border-danger/20 bg-danger/5", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("p", { className: "text-sm text-danger", children: pageNotice ?? combinedError }), pageNotice && (_jsx(Button, { variant: "ghost", size: "sm", className: "text-danger hover:bg-danger/10", onClick: () => setPageNotice(null), children: "Dismiss" }))] }) })), editorOpen || editingId || editingTaskId ? (editorMode === "task" || editingTaskId ? (_jsx(TaskForm, {})) : (_jsx(HeartbeatForm, { form: form, editingId: editingId, editorEnabled: editorEnabled, modalTitle: modalTitle, formError: formError, triggersSaving: triggersSaving, templateNotice: templateNotice, triggers: triggers, triggerRunsById: triggerRunsById, t: t, selectedTriggerId: editingId, setField: setField, setForm: setForm, setFormError: setFormError, closeEditor: closeEditor, onSubmit: onSubmitTrigger, onDelete: onDeleteTrigger, onRunSelectedTrigger: onRunSelectedTrigger, onToggleTriggerEnabled: onToggleTriggerEnabled, saveFormAsTemplate: saveFormAsTemplate, loadTriggerRuns: loadTriggerRuns, kickerLabelCreate: form.kind === "workflow" ? "New schedule" : "New task", kickerLabelEdit: form.kind === "workflow" ? "Edit schedule" : "Edit task", submitLabelCreate: form.kind === "workflow" ? "Create schedule" : "Create task", submitLabelEdit: form.kind === "workflow" ? "Save schedule" : "Save task" }))) : activeSubpage === "node-catalog" ? (_jsx(AutomationNodeCatalogPane, { nodes: automationNodes })) : showDashboard ? (_jsx(AutomationsDashboard, { items: ctx.allItems, onSelectItem: selectItem, onCreateTask: handleZeroStateNewTask, onCreateWorkflow: () => void createWorkflowDraft(), onUseIdea: (idea) => {
|
|
1970
2222
|
if (idea.kind === "workflow") {
|
|
1971
2223
|
void createWorkflowDraft({
|
|
1972
2224
|
title: idea.label,
|
|
@@ -1974,17 +2226,14 @@ function AutomationsLayout() {
|
|
|
1974
2226
|
});
|
|
1975
2227
|
return;
|
|
1976
2228
|
}
|
|
1977
|
-
|
|
1978
|
-
} })) : resolvedSelectedItem?.type === "automation_draft" ? (_jsx(AutomationDraftPane, { automation: resolvedSelectedItem, onSeedPrompt: (prompt) => prefillPageChat(prompt, { select: true }) })) : resolvedSelectedItem?.type === "n8n_workflow" ? (_jsx(WorkflowAutomationDetailPane, { automation: resolvedSelectedItem, n8nStatus: n8nStatus, workflowFetchError: workflowFetchError, workflowBusyId: workflowBusyId, workflowOpsBusy: workflowOpsBusy, onDeleteWorkflow: handleDeleteWorkflow, onDuplicateWorkflow: handleDuplicateWorkflow, onRefreshWorkflows: handleRefreshWorkflows, onStartLocalN8n: handleStartLocalN8n, onToggleWorkflowActive: handleToggleWorkflowActive })) : resolvedSelectedItem?.trigger ? (_jsx(TriggerAutomationDetailPane, { automation: resolvedSelectedItem, onPromoteToWorkflow: promoteAutomationToWorkflow })) : resolvedSelectedItem?.task ? (_jsx(TaskAutomationDetailPane, { automation: resolvedSelectedItem, onPromoteToWorkflow: promoteAutomationToWorkflow })) : showFirstRunEmptyState ? (_jsx(AutomationsZeroState, { onBrowseTemplates: () => setTemplatesModalOpen(true), onNewTrigger: handleZeroStateNewTrigger, onNewTask: handleZeroStateNewTask })) : (_jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center px-8 py-10 text-center", children: _jsx("div", { className: "space-y-3", children: _jsx("h3", { className: "text-lg font-semibold text-txt-strong", children: "Select a task or workflow" }) }) }))] }), _jsx(CreateAutomationDialog, { open: createDialogMode !== null,
|
|
2229
|
+
openSeededTask(idea);
|
|
2230
|
+
} })) : resolvedSelectedItem?.type === "automation_draft" ? (_jsx(AutomationDraftPane, { automation: resolvedSelectedItem, onSeedPrompt: (prompt) => prefillPageChat(prompt, { select: true }), onDeleteDraft: handleDeleteDraft })) : resolvedSelectedItem?.type === "n8n_workflow" ? (_jsx(WorkflowAutomationDetailPane, { automation: resolvedSelectedItem, n8nStatus: n8nStatus, workflowFetchError: workflowFetchError, workflowBusyId: workflowBusyId, workflowOpsBusy: workflowOpsBusy, onDeleteDraft: handleDeleteDraft, onDeleteWorkflow: handleDeleteWorkflow, onDuplicateWorkflow: handleDuplicateWorkflow, onGenerateWorkflow: handleGenerateWorkflow, onRefreshWorkflows: handleRefreshWorkflows, onScheduleWorkflow: handleScheduleWorkflow, onStartLocalN8n: handleStartLocalN8n, onToggleWorkflowActive: handleToggleWorkflowActive })) : resolvedSelectedItem?.trigger ? (_jsx(TriggerAutomationDetailPane, { automation: resolvedSelectedItem, onPromoteToWorkflow: promoteAutomationToWorkflow })) : resolvedSelectedItem?.task ? (_jsx(TaskAutomationDetailPane, { automation: resolvedSelectedItem, onPromoteToWorkflow: promoteAutomationToWorkflow })) : showFirstRunEmptyState ? (_jsx(AutomationsZeroState, { onBrowseTemplates: () => setTemplatesModalOpen(true), onNewTrigger: handleZeroStateNewTrigger, onNewTask: handleZeroStateNewTask })) : (_jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center px-8 py-10 text-center", children: _jsx("div", { className: "space-y-3", children: _jsx("h3", { className: "text-lg font-semibold text-txt-strong", children: "Select a task or workflow" }) }) }))] }), _jsx(CreateAutomationDialog, { open: createDialogMode !== null, onOpenChange: (open) => {
|
|
1979
2231
|
if (!open) {
|
|
1980
2232
|
setCreateDialogMode(null);
|
|
1981
2233
|
}
|
|
1982
2234
|
}, onCreateTask: () => {
|
|
1983
2235
|
setCreateDialogMode(null);
|
|
1984
2236
|
handleZeroStateNewTask();
|
|
1985
|
-
}, onCreateScheduledTask: () => {
|
|
1986
|
-
setCreateDialogMode(null);
|
|
1987
|
-
handleZeroStateNewTrigger();
|
|
1988
2237
|
}, onCreateWorkflow: () => {
|
|
1989
2238
|
setCreateDialogMode(null);
|
|
1990
2239
|
void createWorkflowDraft();
|
|
@@ -1999,5 +2248,5 @@ export function AutomationsView() {
|
|
|
1999
2248
|
}
|
|
2000
2249
|
export function AutomationsDesktopShell() {
|
|
2001
2250
|
const controller = useAutomationsViewController();
|
|
2002
|
-
return (_jsx(AutomationsViewContext.Provider, { value: controller, children: _jsx(AppWorkspaceChrome, { testId: "automations-workspace", chat: _jsx(AutomationsSidebarChat, {}), main: _jsx("div", { className: "flex flex-col flex-1 min-h-0 min-w-0 overflow-hidden", children: _jsx(AutomationsLayout, {}) }) }) }));
|
|
2251
|
+
return (_jsx(AutomationsViewContext.Provider, { value: controller, children: _jsx(AppWorkspaceChrome, { testId: "automations-workspace", chat: _jsx(AutomationsSidebarChat, { activeItem: controller.resolvedSelectedItem }), main: _jsx("div", { className: "flex flex-col flex-1 min-h-0 min-w-0 overflow-hidden", children: _jsx(AutomationsLayout, {}) }) }) }));
|
|
2003
2252
|
}
|