loopable-cli 0.2.0 → 0.3.0
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/.output/nitro.json +1 -1
- package/.output/public/assets/_connectorId-CDCuSgpN.js +1 -0
- package/.output/public/assets/_loopId-DOnIsVTW.js +1 -0
- package/.output/public/assets/_taskId-BO5MZnlZ.js +1 -0
- package/.output/public/assets/agents-CBozi7oi.js +1 -0
- package/.output/public/assets/alert-Bn_wjgTX.js +1 -0
- package/.output/public/assets/button-K3ypIhAZ.js +1 -0
- package/.output/public/assets/card-DiVnaTIE.js +1 -0
- package/.output/public/assets/chevron-right-BrctLtFK.js +1 -0
- package/.output/public/assets/connection-status-DKWelvLO.js +1 -0
- package/.output/public/assets/connector-icon-CEbdE71G.js +1 -0
- package/.output/public/assets/connectors-JyX3IzJj.js +1 -0
- package/.output/public/assets/gaps-PjmcbaWo.js +1 -0
- package/.output/public/assets/inbox-CujRR0Cz.js +1 -0
- package/.output/public/assets/index-C9SQ_Baq.js +16 -0
- package/.output/public/assets/loop-form-Dw3cuTzw.js +1 -0
- package/.output/public/assets/loops-DcEfZa2_.js +1 -0
- package/.output/public/assets/manifests-yJpevyye.js +1 -0
- package/.output/public/assets/new-CwsnNuRW.js +1 -0
- package/.output/public/assets/page-bvnfRMum.js +1 -0
- package/.output/public/assets/play-yHLs4DjK.js +1 -0
- package/.output/public/assets/plus-Ber25lrU.js +1 -0
- package/.output/public/assets/preload-helper-BoU_7EQC.js +3 -0
- package/.output/public/assets/refresh-cw-BzSY7M8I.js +1 -0
- package/.output/public/assets/rotate-ccw-DLv9EfCX.js +1 -0
- package/.output/public/assets/runners-DzX-RU3i.js +2 -0
- package/.output/public/assets/select-C9grIFwG.js +1 -0
- package/.output/public/assets/setting-fields-DUc3npgh.js +3 -0
- package/.output/public/assets/styles-B9B98zLk.css +2 -0
- package/.output/public/assets/switch-DwABVI6d.js +1 -0
- package/.output/public/assets/task-list-Ddk8-qt7.js +1 -0
- package/.output/public/assets/textarea-Ds-gIYUQ.js +1 -0
- package/.output/public/assets/time-goBWd-5J.js +1 -0
- package/.output/public/assets/use-live-tasks-DZNNuECy.js +1 -0
- package/.output/public/assets/useCompositeListItem-P4Nmmn7j.js +1 -0
- package/.output/public/assets/usePositioner-B7i9Ejzh.js +1 -0
- package/.output/server/_chunks/ssr-renderer.mjs +1 -1
- package/.output/server/_connectorId-COuQN02a.mjs +512 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +1410 -804
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/lucide-react.mjs +93 -84
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_loopId-CZh6_Yhr.mjs +310 -0
- package/.output/server/_ssr/{callback-D20cVD2w.mjs → access-DTttr563.mjs} +37 -20
- package/.output/server/_ssr/{agents-CrUmIQpi.mjs → agents-B_qo1gpL.mjs} +196 -7
- package/.output/server/_ssr/{agents-Dms_6TBF.mjs → agents-Bz10gz_m.mjs} +8 -8
- package/.output/server/_ssr/agents-CjbOUp3Z.mjs +206 -0
- package/.output/server/_ssr/{alert-DQvzE7bu.mjs → alert-C2WBWfzo.mjs} +2 -2
- package/.output/server/_ssr/card-r1cV-PCN.mjs +36 -0
- package/.output/server/_ssr/{client-BtVRVhQ2.mjs → client-Dk3SD8Il.mjs} +5 -1
- package/.output/server/_ssr/connection-status-BogAL-Fj.mjs +32 -0
- package/.output/server/_ssr/{connections-ypex3qUd.mjs → connections-Cqvb6Gw2.mjs} +1109 -263
- package/.output/server/_ssr/connector-icon-DrhVbhs6.mjs +190 -0
- package/.output/server/_ssr/connectors-BfS5tQKr.mjs +81 -0
- package/.output/server/_ssr/{connectors-BZly0yGF.mjs → connectors-DJI-rzPU.mjs} +21 -10
- package/.output/server/_ssr/{createServerRpc-DPDrnFdH.mjs → createServerRpc-DDrp2nLT.mjs} +2 -2
- package/.output/server/_ssr/{gaps-D2Zh8sEo.mjs → gaps-CcvHiLcj.mjs} +18 -10
- package/.output/server/_ssr/inbox-D1ef761v.mjs +178 -0
- package/.output/server/_ssr/loop-form-DWwOR05R.mjs +346 -0
- package/.output/server/_ssr/{loops-Og_G3KyZ.mjs → loops-6WhUe_lc.mjs} +228 -25
- package/.output/server/_ssr/loops-DHPjzG-t.mjs +217 -0
- package/.output/server/_ssr/new-ChKBdcXJ.mjs +245 -0
- package/.output/server/_ssr/page-DZVxKvdn.mjs +120 -0
- package/.output/server/_ssr/{router-BbrBz36P.mjs → router-DdwREiOf.mjs} +167 -489
- package/.output/server/_ssr/runners-CK9Cx9fS.mjs +217 -0
- package/.output/server/_ssr/runners-CrznFPHt.mjs +140 -0
- package/.output/server/_ssr/runners-W-pgo5yo.mjs +55 -0
- package/.output/server/_ssr/{secrets-CRnpwGBh.mjs → secrets-CV5scFlt.mjs} +2 -2
- package/.output/server/_ssr/select-BtndHhHb.mjs +76 -0
- package/.output/server/_ssr/{setting-fields-mpXfGftI.mjs → setting-fields-LNgywAW7.mjs} +25 -20
- package/.output/server/_ssr/ssr.mjs +49 -40
- package/.output/server/_ssr/{start-BgTj-h_C.mjs → start-C_j-e4vF.mjs} +10 -6
- package/.output/server/_ssr/{switch-1pnd_bPJ.mjs → switch-odxNO_S4.mjs} +3 -3
- package/.output/server/_ssr/task-list-Dnmq_WeE.mjs +150 -0
- package/.output/server/_ssr/{tasks-LIi-W4cy.mjs → tasks-D-kXLgVP.mjs} +13 -13
- package/.output/server/_ssr/{tasks-B_QmDFVs.mjs → tasks-D7aUvA33.mjs} +55 -17
- package/.output/server/_ssr/{textarea-CHJTKtsJ.mjs → textarea-D2c_vTIC.mjs} +2 -2
- package/.output/server/_ssr/time-Cy8TZP0Z.mjs +13 -0
- package/.output/server/_ssr/use-live-tasks-BzhTkEOL.mjs +40 -0
- package/.output/server/_tanstack-start-manifest_v-2HUdn-2E.mjs +166 -0
- package/.output/server/_taskId-D3KkNJW1.mjs +263 -0
- package/.output/server/index.mjs +204 -162
- package/README.md +54 -19
- package/dist/cli.mjs +185 -67
- package/dist/dispatcher.mjs +1443 -277
- package/dist/runner.mjs +333 -46
- package/drizzle/0025_prompt_tasks_done.sql +2 -0
- package/drizzle/0026_guidance_into_prompt.sql +3 -0
- package/drizzle/0027_relative_folders.sql +5 -0
- package/drizzle/0028_runner_version.sql +5 -0
- package/drizzle/meta/_journal.json +29 -1
- package/package.json +23 -17
- package/.output/public/assets/_connectorId-m67WbiJ8.js +0 -1
- package/.output/public/assets/_loopId-Bf4K5sdd.js +0 -1
- package/.output/public/assets/_taskId-eCKkcVOx.js +0 -1
- package/.output/public/assets/alert-BT2EVMJP.js +0 -1
- package/.output/public/assets/arrow-left-CWUZt6k2.js +0 -1
- package/.output/public/assets/arrow-right-dy_oOg_c.js +0 -1
- package/.output/public/assets/connection-status-DCqqmY_6.js +0 -1
- package/.output/public/assets/connector-icon-qGpNsPgH.js +0 -1
- package/.output/public/assets/connectors-DxXlc9zF.js +0 -1
- package/.output/public/assets/external-link-DC11orFA.js +0 -1
- package/.output/public/assets/gaps-CoTA-PzA.js +0 -1
- package/.output/public/assets/inbox-CLEXUXCI.js +0 -1
- package/.output/public/assets/index-Cndsw84p.js +0 -16
- package/.output/public/assets/loader-B4Epchm_.js +0 -1
- package/.output/public/assets/loop-form-BvizlZdR.js +0 -1
- package/.output/public/assets/loops-Dbe_mf5T.js +0 -1
- package/.output/public/assets/new-DPEaXKAY.js +0 -1
- package/.output/public/assets/play-DD4xQgvi.js +0 -1
- package/.output/public/assets/preload-helper-CfdAYKD7.js +0 -3
- package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +0 -1
- package/.output/public/assets/runners-BVBggS5B.js +0 -1
- package/.output/public/assets/select-Cppu-hti.js +0 -1
- package/.output/public/assets/setting-fields-BT1naVJX.js +0 -3
- package/.output/public/assets/styles-D3sonCfX.css +0 -2
- package/.output/public/assets/switch-hRIbNGOj.js +0 -1
- package/.output/public/assets/task-list-CrptnI1N.js +0 -1
- package/.output/public/assets/textarea-Fyuzi6Vp.js +0 -1
- package/.output/public/assets/trash-2-BXZzBsoV.js +0 -1
- package/.output/public/assets/useValueChanged-VVyq4hb9.js +0 -1
- package/.output/server/_connectorId-DcSnoRjk.mjs +0 -421
- package/.output/server/_loopId-G_TAkYtS.mjs +0 -272
- package/.output/server/_ssr/access-B7TcOZ7F.mjs +0 -42
- package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +0 -31
- package/.output/server/_ssr/connector-icon-CFrrHjzR.mjs +0 -37
- package/.output/server/_ssr/connectors-4yzKb0KG.mjs +0 -106
- package/.output/server/_ssr/inbox-C2VBGLa5.mjs +0 -114
- package/.output/server/_ssr/loop-form-D-U-kU4k.mjs +0 -413
- package/.output/server/_ssr/loops-D0XJ8eBb.mjs +0 -222
- package/.output/server/_ssr/new-CajvgIog.mjs +0 -169
- package/.output/server/_ssr/runners-B_WuF8UJ.mjs +0 -32
- package/.output/server/_ssr/runners-D-0_lfiq.mjs +0 -106
- package/.output/server/_ssr/runners-zqF_zbEc.mjs +0 -116
- package/.output/server/_ssr/task-list-BnQJ94Dk.mjs +0 -187
- package/.output/server/_tanstack-start-manifest_v-BCHAazE8.mjs +0 -129
- package/.output/server/_taskId-CIv0ajIx.mjs +0 -256
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as createServerFn } from "./ssr.mjs";
|
|
2
2
|
import { d as and, f as eq, l as asc, p as inArray, v as sql } from "../_libs/drizzle-orm.mjs";
|
|
3
|
-
import { a as connections, c as loops, h as signals, s as db } from "./client-
|
|
4
|
-
import {
|
|
5
|
-
import { n as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { t as createServerRpc } from "./createServerRpc-
|
|
3
|
+
import { a as connections, c as loops, h as signals, s as db } from "./client-Dk3SD8Il.mjs";
|
|
4
|
+
import { c as connectorRuntime, l as connectorWorkflow, o as connectorAction, s as connectorManifest, u as credentialForConnector } from "./connections-Cqvb6Gw2.mjs";
|
|
5
|
+
import { n as availableAgentIds } from "./runners-CK9Cx9fS.mjs";
|
|
6
|
+
import { i as listAgents, n as agentManifest, r as agentRuntime, s as settingsFor } from "./agents-B_qo1gpL.mjs";
|
|
7
|
+
import { i as enqueueSignal, n as defaultAgentFor, o as folderIsLegacy, p as normalizeFolder } from "./tasks-D7aUvA33.mjs";
|
|
8
|
+
import { t as createServerRpc } from "./createServerRpc-DDrp2nLT.mjs";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
|
-
|
|
10
|
+
import { mkdtempSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/loops-6WhUe_lc.js
|
|
11
14
|
var NAME_LIMIT = 80;
|
|
12
15
|
var PROMPT_LIMIT = 8e3;
|
|
13
16
|
var GUIDANCE_LIMIT = 4e3;
|
|
@@ -73,16 +76,22 @@ function validate(draft) {
|
|
|
73
76
|
if (prompt.length > PROMPT_LIMIT) throw new Error(`Keep what you ask under ${PROMPT_LIMIT} characters.`);
|
|
74
77
|
const guidance = draft.guidance?.trim() || null;
|
|
75
78
|
if (guidance && guidance.length > GUIDANCE_LIMIT) throw new Error(`Keep the guidance under ${GUIDANCE_LIMIT} characters.`);
|
|
79
|
+
let folder;
|
|
76
80
|
if (workflow.runsIn === "folder") {
|
|
77
|
-
const
|
|
78
|
-
|
|
81
|
+
const given = typeof draft.settings.folder === "string" ? draft.settings.folder : "";
|
|
82
|
+
folder = normalizeFolder(given);
|
|
83
|
+
if (!folder) throw new Error("Say which folder this should work in.");
|
|
84
|
+
if (folderIsLegacy(folder)) throw new Error("Give the folder relative to the runner's home, such as code/web.");
|
|
79
85
|
}
|
|
80
86
|
return {
|
|
81
87
|
...draft,
|
|
82
88
|
name,
|
|
83
89
|
prompt,
|
|
84
90
|
guidance,
|
|
85
|
-
settings:
|
|
91
|
+
settings: {
|
|
92
|
+
...cleanConditions(workflow.settings, draft.settings),
|
|
93
|
+
...folder ? { folder } : {}
|
|
94
|
+
},
|
|
86
95
|
actionTarget: cleanConditions(action.target, draft.actionTarget)
|
|
87
96
|
};
|
|
88
97
|
}
|
|
@@ -173,15 +182,202 @@ async function loopReadiness() {
|
|
|
173
182
|
const connected = await db().selectDistinct({ connectorId: connections.connectorId }).from(connections).all();
|
|
174
183
|
const available = await availableAgentIds();
|
|
175
184
|
const host = await availableAgentIds(true);
|
|
185
|
+
const folder = await availableAgentIds(false, 2);
|
|
176
186
|
const chosen = agents.find((agent) => agent.isDefault)?.agentId ?? null;
|
|
177
187
|
const defaultAgentId = (chosen && available.includes(chosen) ? chosen : null) ?? (available.length === 1 ? available[0] : null);
|
|
178
188
|
return {
|
|
179
189
|
connectedConnectorIds: connected.map((row) => row.connectorId),
|
|
180
190
|
defaultAgentId,
|
|
181
191
|
availableAgentIds: available,
|
|
182
|
-
hostAgentIds: host
|
|
192
|
+
hostAgentIds: host,
|
|
193
|
+
folderAgentIds: folder
|
|
183
194
|
};
|
|
184
195
|
}
|
|
196
|
+
var NOTHING_ASKED = "nothing in it was being asked of you";
|
|
197
|
+
/**
|
|
198
|
+
* The batch is numbered rather than named because the agent has to point back
|
|
199
|
+
* at these, and a subject line is not a usable handle: two mails can share
|
|
200
|
+
* one, and any of them can contain the quotes and newlines that would break
|
|
201
|
+
* the answer apart.
|
|
202
|
+
*/
|
|
203
|
+
function listFor(signals) {
|
|
204
|
+
return signals.map((signal, index) => {
|
|
205
|
+
const preview = signal.preview ? `\n ${signal.preview.slice(0, 300)}` : "";
|
|
206
|
+
return `${index + 1}. ${signal.title}${preview}`;
|
|
207
|
+
}).join("\n\n");
|
|
208
|
+
}
|
|
209
|
+
function promptFor(signals, guidance) {
|
|
210
|
+
const lines = [
|
|
211
|
+
"Below are things that just arrived. Say which ones are asking something",
|
|
212
|
+
"of the person who received them.",
|
|
213
|
+
"",
|
|
214
|
+
"Asking something means there is a decision to make, a reply expected, a",
|
|
215
|
+
"form to fill, a payment due, a meeting to book, a deadline, or a problem",
|
|
216
|
+
"to look at. Receipts, statements, newsletters, marketing, social",
|
|
217
|
+
"notifications, and automated reports are not asking anything, even when",
|
|
218
|
+
"they are about money, and even when they are worth having read.",
|
|
219
|
+
"",
|
|
220
|
+
"When you cannot tell from what is here, treat it as asking something.",
|
|
221
|
+
"Being told about one thing too many is a small cost; missing the one that",
|
|
222
|
+
"mattered is not."
|
|
223
|
+
];
|
|
224
|
+
if (guidance) lines.push("", "What this person has said they care about, which outranks the above:", guidance);
|
|
225
|
+
lines.push("", "Answer with JSON only, no other text: {\"needsMe\": [1, 4]}, listing the", "numbers that are asking something. An empty list is a valid answer.", "", listFor(signals));
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The numbers the agent picked out, as indices. Anything unparseable gives
|
|
230
|
+
* nothing back, which the caller reads as "could not tell" rather than as
|
|
231
|
+
* "none of them": a triage that failed must not be the reason work went
|
|
232
|
+
* unnoticed.
|
|
233
|
+
*/
|
|
234
|
+
function parseAnswer(output, count) {
|
|
235
|
+
const start = output.indexOf("{");
|
|
236
|
+
const end = output.lastIndexOf("}");
|
|
237
|
+
if (start === -1 || end <= start) return null;
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(output.slice(start, end + 1));
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const listed = parsed?.needsMe;
|
|
245
|
+
if (!Array.isArray(listed)) return null;
|
|
246
|
+
const chosen = /* @__PURE__ */ new Set();
|
|
247
|
+
for (const entry of listed) {
|
|
248
|
+
const value = typeof entry === "number" ? entry : Number.parseInt(String(entry), 10);
|
|
249
|
+
if (Number.isInteger(value) && value >= 1 && value <= count) chosen.add(value - 1);
|
|
250
|
+
}
|
|
251
|
+
return chosen;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Which of these need not be run, and why. An empty result means everything
|
|
255
|
+
* runs, which is what happens whenever the answer cannot be trusted: too few
|
|
256
|
+
* to be worth a run, an agent that failed, or an answer that made no sense.
|
|
257
|
+
*/
|
|
258
|
+
async function triage(input) {
|
|
259
|
+
const held = /* @__PURE__ */ new Map();
|
|
260
|
+
if (input.signals.length < 2) return held;
|
|
261
|
+
const result = await agentRuntime(input.agentId).run({
|
|
262
|
+
prompt: promptFor(input.signals, input.guidance),
|
|
263
|
+
cwd: input.cwd,
|
|
264
|
+
settings: await settingsFor(input.agentId),
|
|
265
|
+
signal: input.signal
|
|
266
|
+
});
|
|
267
|
+
if (!result.ok || result.aborted) return held;
|
|
268
|
+
const needsMe = parseAnswer(result.output, input.signals.length);
|
|
269
|
+
if (!needsMe) return held;
|
|
270
|
+
input.signals.forEach((entry, index) => {
|
|
271
|
+
if (!needsMe.has(index)) held.set(entry.key, NOTHING_ASKED);
|
|
272
|
+
});
|
|
273
|
+
return held;
|
|
274
|
+
}
|
|
275
|
+
/** A setting a loop was saved before existing is missing, which is not false. */
|
|
276
|
+
function boolean(value) {
|
|
277
|
+
return value === true;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Two loops can want the same pull request. Only the first one gets it, so
|
|
281
|
+
* this is what one loop tells the next about what it has taken.
|
|
282
|
+
*/
|
|
283
|
+
function claimOf(loop, signal) {
|
|
284
|
+
return `${loop.connectorId}:${signal.ref}`;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* One look at what a loop is watching for.
|
|
288
|
+
*
|
|
289
|
+
* The first look never acts. Whatever is already waiting when a loop is
|
|
290
|
+
* created is that loop's backlog, and running an agent over a review queue
|
|
291
|
+
* that has been piling up for a month is not what someone turning on a loop
|
|
292
|
+
* is asking for. It is recorded rather than discarded, so it can still be run
|
|
293
|
+
* on purpose afterwards.
|
|
294
|
+
*/
|
|
295
|
+
async function pollLoop(loop, claimed) {
|
|
296
|
+
const report = {
|
|
297
|
+
loopId: loop.id,
|
|
298
|
+
loopName: loop.name,
|
|
299
|
+
found: 0,
|
|
300
|
+
queued: 0,
|
|
301
|
+
backlog: 0,
|
|
302
|
+
held: 0,
|
|
303
|
+
superseded: 0,
|
|
304
|
+
triaged: 0,
|
|
305
|
+
error: null
|
|
306
|
+
};
|
|
307
|
+
try {
|
|
308
|
+
const manifest = connectorManifest(loop.connectorId);
|
|
309
|
+
if (!manifest) throw new Error(`Unknown connector: ${loop.connectorId}`);
|
|
310
|
+
const workflow = connectorWorkflow(loop.connectorId, loop.workflowId);
|
|
311
|
+
if (!workflow) throw new Error(`${manifest.name} no longer offers ${loop.workflowId}.`);
|
|
312
|
+
const runtime = connectorRuntime(loop.connectorId);
|
|
313
|
+
if (!runtime.poll) throw new Error(`${manifest.name} cannot watch for anything yet.`);
|
|
314
|
+
const { credential } = await credentialForConnector(loop.connectorId);
|
|
315
|
+
const answer = await runtime.poll({
|
|
316
|
+
workflowId: workflow.triggerId,
|
|
317
|
+
settings: loop.settings,
|
|
318
|
+
credential,
|
|
319
|
+
cursor: loop.pollCursor ?? null
|
|
320
|
+
});
|
|
321
|
+
const found = answer.signals;
|
|
322
|
+
report.found = found.length;
|
|
323
|
+
const known = new Set((await db().select({ key: signals.key }).from(signals).where(eq(signals.loopId, loop.id)).all()).map((row) => row.key));
|
|
324
|
+
const first = loop.polledAt === null;
|
|
325
|
+
let judged = /* @__PURE__ */ new Map();
|
|
326
|
+
if (!first && boolean(loop.settings.needsMeOnly)) judged = await triage({
|
|
327
|
+
signals: found.filter((signal) => !known.has(signal.key) && !signal.hold && !claimed.has(claimOf(loop, signal))),
|
|
328
|
+
agentId: await defaultAgentFor(loop.agentId),
|
|
329
|
+
guidance: loop.guidance,
|
|
330
|
+
cwd: mkdtempSync(join(tmpdir(), "loopable-triage-"))
|
|
331
|
+
});
|
|
332
|
+
for (const signal of found) {
|
|
333
|
+
const claim = claimOf(loop, signal);
|
|
334
|
+
if (known.has(signal.key)) {
|
|
335
|
+
claimed.add(claim);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const hold = signal.hold ?? judged.get(signal.key);
|
|
339
|
+
const outcome = claimed.has(claim) ? "superseded" : hold ? "held" : first ? "backlog" : "queued";
|
|
340
|
+
const task = outcome === "queued" ? await enqueueSignal({
|
|
341
|
+
loop,
|
|
342
|
+
signal
|
|
343
|
+
}) : null;
|
|
344
|
+
await db().insert(signals).values({
|
|
345
|
+
loopId: loop.id,
|
|
346
|
+
key: signal.key,
|
|
347
|
+
outcome,
|
|
348
|
+
sourceKind: signal.kind,
|
|
349
|
+
sourceRef: signal.ref,
|
|
350
|
+
sourceTitle: signal.title,
|
|
351
|
+
sourceUrl: signal.url,
|
|
352
|
+
hold,
|
|
353
|
+
sourcePayload: signal.payload,
|
|
354
|
+
taskId: task?.id
|
|
355
|
+
}).onConflictDoNothing().run();
|
|
356
|
+
claimed.add(claim);
|
|
357
|
+
report[outcome] += 1;
|
|
358
|
+
if (outcome === "held" && !signal.hold) report.triaged += 1;
|
|
359
|
+
}
|
|
360
|
+
await db().update(loops).set({
|
|
361
|
+
polledAt: /* @__PURE__ */ new Date(),
|
|
362
|
+
pollError: null,
|
|
363
|
+
...answer.cursor !== void 0 ? { pollCursor: answer.cursor } : {}
|
|
364
|
+
}).where(eq(loops.id, loop.id)).run();
|
|
365
|
+
} catch (error) {
|
|
366
|
+
report.error = error instanceof Error ? error.message : String(error);
|
|
367
|
+
await db().update(loops).set({ pollError: report.error }).where(eq(loops.id, loop.id)).run();
|
|
368
|
+
}
|
|
369
|
+
return report;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* One look, now, whether the interval says it is due or not. The loop page
|
|
373
|
+
* asks for this when waiting for the dispatcher is the thing being tested.
|
|
374
|
+
*/
|
|
375
|
+
async function lookNow(loopId) {
|
|
376
|
+
const loop = await db().select().from(loops).where(eq(loops.id, loopId)).get();
|
|
377
|
+
if (!loop) throw new Error("Loop not found");
|
|
378
|
+
if (!loop.enabled) throw new Error("This loop is off");
|
|
379
|
+
return await pollLoop(loop, /* @__PURE__ */ new Set());
|
|
380
|
+
}
|
|
185
381
|
function toBacklogItem(row) {
|
|
186
382
|
return {
|
|
187
383
|
key: row.key,
|
|
@@ -211,14 +407,15 @@ async function loopPollState(loopId) {
|
|
|
211
407
|
};
|
|
212
408
|
}
|
|
213
409
|
/**
|
|
214
|
-
* Run
|
|
215
|
-
*
|
|
216
|
-
*
|
|
410
|
+
* Run what the loop is holding: one item when `key` is given, or all of it.
|
|
411
|
+
* Asked for explicitly, because something held back for being too big is
|
|
412
|
+
* being run against the connector's advice.
|
|
217
413
|
*/
|
|
218
|
-
async function runBacklog(loopId) {
|
|
414
|
+
async function runBacklog(loopId, key) {
|
|
219
415
|
const loop = await db().select().from(loops).where(eq(loops.id, loopId)).get();
|
|
220
416
|
if (!loop) throw new Error("Loop not found");
|
|
221
|
-
const waiting = await db().select().from(signals).where(and(eq(signals.loopId, loopId), inArray(signals.outcome, [...WAITING]))).all();
|
|
417
|
+
const waiting = await db().select().from(signals).where(and(eq(signals.loopId, loopId), inArray(signals.outcome, [...WAITING]), key === void 0 ? void 0 : eq(signals.key, key))).all();
|
|
418
|
+
if (key !== void 0 && waiting.length === 0) throw new Error("That item is no longer waiting.");
|
|
222
419
|
for (const row of waiting) {
|
|
223
420
|
const task = await enqueueSignal({
|
|
224
421
|
loop,
|
|
@@ -264,13 +461,13 @@ var getLoopById_createServerFn_handler = createServerRpc({
|
|
|
264
461
|
name: "getLoopById",
|
|
265
462
|
filename: "src/server/functions/loops.ts"
|
|
266
463
|
}, (opts) => getLoopById.__executeServer(opts));
|
|
267
|
-
var getLoopById = createServerFn({ method: "GET" }).
|
|
464
|
+
var getLoopById = createServerFn({ method: "GET" }).validator((data) => data).handler(getLoopById_createServerFn_handler, async ({ data }) => await getLoop(data.id));
|
|
268
465
|
var saveLoop_createServerFn_handler = createServerRpc({
|
|
269
466
|
id: "e91fc2fd51633c7ca9184fea4beb66fe775ed0b4e6323111a4690da916a78b00",
|
|
270
467
|
name: "saveLoop",
|
|
271
468
|
filename: "src/server/functions/loops.ts"
|
|
272
469
|
}, (opts) => saveLoop.__executeServer(opts));
|
|
273
|
-
var saveLoop = createServerFn({ method: "POST" }).
|
|
470
|
+
var saveLoop = createServerFn({ method: "POST" }).validator(edit).handler(saveLoop_createServerFn_handler, async ({ data }) => {
|
|
274
471
|
const { id, ...values } = data;
|
|
275
472
|
return id ? await updateLoop(id, values) : await createLoop(values);
|
|
276
473
|
});
|
|
@@ -279,13 +476,13 @@ var toggleLoop_createServerFn_handler = createServerRpc({
|
|
|
279
476
|
name: "toggleLoop",
|
|
280
477
|
filename: "src/server/functions/loops.ts"
|
|
281
478
|
}, (opts) => toggleLoop.__executeServer(opts));
|
|
282
|
-
var toggleLoop = createServerFn({ method: "POST" }).
|
|
479
|
+
var toggleLoop = createServerFn({ method: "POST" }).validator((data) => data).handler(toggleLoop_createServerFn_handler, async ({ data }) => await setLoopEnabled(data.id, data.enabled));
|
|
283
480
|
var removeLoop_createServerFn_handler = createServerRpc({
|
|
284
481
|
id: "b5a89ca2d07f4ccf2827b5132ca52e7c7a42d584e702b0ab3963b2d5763c35b0",
|
|
285
482
|
name: "removeLoop",
|
|
286
483
|
filename: "src/server/functions/loops.ts"
|
|
287
484
|
}, (opts) => removeLoop.__executeServer(opts));
|
|
288
|
-
var removeLoop = createServerFn({ method: "POST" }).
|
|
485
|
+
var removeLoop = createServerFn({ method: "POST" }).validator((data) => data).handler(removeLoop_createServerFn_handler, async ({ data }) => {
|
|
289
486
|
await deleteLoop(data.id);
|
|
290
487
|
return await listLoops();
|
|
291
488
|
});
|
|
@@ -294,18 +491,24 @@ var reorderLoop_createServerFn_handler = createServerRpc({
|
|
|
294
491
|
name: "reorderLoop",
|
|
295
492
|
filename: "src/server/functions/loops.ts"
|
|
296
493
|
}, (opts) => reorderLoop.__executeServer(opts));
|
|
297
|
-
var reorderLoop = createServerFn({ method: "POST" }).
|
|
494
|
+
var reorderLoop = createServerFn({ method: "POST" }).validator((data) => data).handler(reorderLoop_createServerFn_handler, async ({ data }) => await moveLoop(data.id, data.direction));
|
|
298
495
|
var getLoopPollState_createServerFn_handler = createServerRpc({
|
|
299
496
|
id: "4fd3855e3e9ed954e102071a55b98d91651c9b1e353161f7223768ed6f2b8c28",
|
|
300
497
|
name: "getLoopPollState",
|
|
301
498
|
filename: "src/server/functions/loops.ts"
|
|
302
499
|
}, (opts) => getLoopPollState.__executeServer(opts));
|
|
303
|
-
var getLoopPollState = createServerFn({ method: "GET" }).
|
|
500
|
+
var getLoopPollState = createServerFn({ method: "GET" }).validator((data) => data).handler(getLoopPollState_createServerFn_handler, async ({ data }) => await loopPollState(data.id));
|
|
304
501
|
var runLoopBacklog_createServerFn_handler = createServerRpc({
|
|
305
502
|
id: "65494cbfd06143dbd9a012808fbba21163d9902e387869503fb8b68b0674902b",
|
|
306
503
|
name: "runLoopBacklog",
|
|
307
504
|
filename: "src/server/functions/loops.ts"
|
|
308
505
|
}, (opts) => runLoopBacklog.__executeServer(opts));
|
|
309
|
-
var runLoopBacklog = createServerFn({ method: "POST" }).
|
|
506
|
+
var runLoopBacklog = createServerFn({ method: "POST" }).validator((data) => data).handler(runLoopBacklog_createServerFn_handler, async ({ data }) => ({ queued: await runBacklog(data.id, data.key) }));
|
|
507
|
+
var lookLoopNow_createServerFn_handler = createServerRpc({
|
|
508
|
+
id: "965ff21ee2f1e1f5a1cb78d50570cbad6f58ccdbdcc7955f02f816a9c0684f8e",
|
|
509
|
+
name: "lookLoopNow",
|
|
510
|
+
filename: "src/server/functions/loops.ts"
|
|
511
|
+
}, (opts) => lookLoopNow.__executeServer(opts));
|
|
512
|
+
var lookLoopNow = createServerFn({ method: "POST" }).validator((data) => data).handler(lookLoopNow_createServerFn_handler, async ({ data }) => await lookNow(data.id));
|
|
310
513
|
//#endregion
|
|
311
|
-
export { getLoopById_createServerFn_handler, getLoopPollState_createServerFn_handler, getLoopReadiness_createServerFn_handler, getLoopsPage_createServerFn_handler, removeLoop_createServerFn_handler, reorderLoop_createServerFn_handler, runLoopBacklog_createServerFn_handler, saveLoop_createServerFn_handler, toggleLoop_createServerFn_handler };
|
|
514
|
+
export { getLoopById_createServerFn_handler, getLoopPollState_createServerFn_handler, getLoopReadiness_createServerFn_handler, getLoopsPage_createServerFn_handler, lookLoopNow_createServerFn_handler, removeLoop_createServerFn_handler, reorderLoop_createServerFn_handler, runLoopBacklog_createServerFn_handler, saveLoop_createServerFn_handler, toggleLoop_createServerFn_handler };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { i as __toESM } from "../_runtime.mjs";
|
|
2
|
+
import { K as require_jsx_runtime, a as MenuPortal, i as MenuPositioner, n as MenuTrigger, o as MenuPopup, q as require_react, r as MenuRoot, s as MenuItem, t as Separator } from "../_libs/@base-ui/react+[...].mjs";
|
|
3
|
+
import { f as Link, m as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
4
|
+
import { d as destinationLabel, l as connectorWorkflow, s as connectorManifest } from "./connections-Cqvb6Gw2.mjs";
|
|
5
|
+
import { n as agentManifest } from "./agents-B_qo1gpL.mjs";
|
|
6
|
+
import { B as ArrowUp, C as Ellipsis, N as CircleAlert, V as ArrowDown, i as Trash2, l as Plus } from "../_libs/lucide-react.mjs";
|
|
7
|
+
import { n as toast } from "../_libs/sonner.mjs";
|
|
8
|
+
import { P as cn, _ as Badge, b as removeLoop, s as Route$11, v as Button$1, w as toggleLoop, x as reorderLoop } from "./router-DdwREiOf.mjs";
|
|
9
|
+
import { t as ConnectorIcon } from "./connector-icon-DrhVbhs6.mjs";
|
|
10
|
+
import { a as List, o as PageHeader, t as Empty } from "./page-DZVxKvdn.mjs";
|
|
11
|
+
import { t as Switch$1 } from "./switch-odxNO_S4.mjs";
|
|
12
|
+
import { n as AlertDescription, r as AlertTitle, t as Alert } from "./alert-C2WBWfzo.mjs";
|
|
13
|
+
import { n as useLiveRefresh } from "./use-live-tasks-BzhTkEOL.mjs";
|
|
14
|
+
import { n as issuesFor, t as gapsFor } from "./gaps-CcvHiLcj.mjs";
|
|
15
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/loops-DHPjzG-t.js
|
|
16
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
17
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
18
|
+
function DropdownMenu({ ...props }) {
|
|
19
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRoot, {
|
|
20
|
+
"data-slot": "dropdown-menu",
|
|
21
|
+
...props
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
25
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuTrigger, {
|
|
26
|
+
"data-slot": "dropdown-menu-trigger",
|
|
27
|
+
...props
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, ...props }) {
|
|
31
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPositioner, {
|
|
32
|
+
className: "isolate z-50 outline-none",
|
|
33
|
+
align,
|
|
34
|
+
alignOffset,
|
|
35
|
+
side,
|
|
36
|
+
sideOffset,
|
|
37
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuPopup, {
|
|
38
|
+
"data-slot": "dropdown-menu-content",
|
|
39
|
+
className: cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className),
|
|
40
|
+
...props
|
|
41
|
+
})
|
|
42
|
+
}) });
|
|
43
|
+
}
|
|
44
|
+
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
45
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
|
|
46
|
+
"data-slot": "dropdown-menu-item",
|
|
47
|
+
"data-inset": inset,
|
|
48
|
+
"data-variant": variant,
|
|
49
|
+
className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive", className),
|
|
50
|
+
...props
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
54
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, {
|
|
55
|
+
"data-slot": "dropdown-menu-separator",
|
|
56
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
57
|
+
...props
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function LoopsPage() {
|
|
61
|
+
const { loops, readiness } = Route$11.useLoaderData();
|
|
62
|
+
useLiveRefresh();
|
|
63
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
64
|
+
className: "flex flex-col gap-6",
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageHeader, {
|
|
67
|
+
title: "Loops",
|
|
68
|
+
description: "When two loops match the same thing, the higher one runs.",
|
|
69
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$1, {
|
|
70
|
+
nativeButton: false,
|
|
71
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
|
|
72
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, {}), "New loop"]
|
|
73
|
+
})
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Gaps, {
|
|
76
|
+
readiness,
|
|
77
|
+
loops
|
|
78
|
+
}),
|
|
79
|
+
loops.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
|
|
80
|
+
action: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
|
|
81
|
+
variant: "outline",
|
|
82
|
+
nativeButton: false,
|
|
83
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Link, { to: "/loops/new" }),
|
|
84
|
+
children: "New loop"
|
|
85
|
+
}),
|
|
86
|
+
children: "No loops yet."
|
|
87
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { children: loops.map((loop, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoopRow, {
|
|
88
|
+
loop,
|
|
89
|
+
readiness,
|
|
90
|
+
position: index + 1,
|
|
91
|
+
first: index === 0,
|
|
92
|
+
last: index === loops.length - 1
|
|
93
|
+
}, loop.id)) })
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** What still stands between these loops and running. */
|
|
98
|
+
function Gaps({ readiness, loops }) {
|
|
99
|
+
const missing = gapsFor(readiness, loops);
|
|
100
|
+
if (missing.length === 0) return null;
|
|
101
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Alert, { children: [
|
|
102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleAlert, {}),
|
|
103
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertTitle, { children: "Nothing will run yet" }),
|
|
104
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, {
|
|
105
|
+
className: "flex flex-col gap-0.5",
|
|
106
|
+
children: missing.map((line) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: line }, line))
|
|
107
|
+
})
|
|
108
|
+
] });
|
|
109
|
+
}
|
|
110
|
+
/** Where this loop answers and with what, in a few words. */
|
|
111
|
+
function summaryOf(loop) {
|
|
112
|
+
if (!connectorWorkflow(loop.connectorId, loop.workflowId)) return "Workflow no longer offered";
|
|
113
|
+
const agent = loop.agentId ? agentManifest(loop.agentId) : void 0;
|
|
114
|
+
return [destinationLabel(loop.actionConnectorId, loop.actionId), agent?.name ?? "Default agent"].join(" · ");
|
|
115
|
+
}
|
|
116
|
+
function LoopRow({ loop, readiness, position, first, last }) {
|
|
117
|
+
const router = useRouter();
|
|
118
|
+
const manifest = connectorManifest(loop.connectorId);
|
|
119
|
+
const issues = issuesFor(readiness, loop);
|
|
120
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
121
|
+
const run = async (work, message) => {
|
|
122
|
+
setBusy(true);
|
|
123
|
+
try {
|
|
124
|
+
await work();
|
|
125
|
+
await router.invalidate();
|
|
126
|
+
if (message) toast.success(message);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
toast.error(error instanceof Error ? error.message : String(error));
|
|
129
|
+
} finally {
|
|
130
|
+
setBusy(false);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
134
|
+
className: "flex items-center gap-3 px-4 py-3",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
137
|
+
className: "w-4 shrink-0 text-center text-xs text-muted-foreground tabular-nums",
|
|
138
|
+
children: position
|
|
139
|
+
}),
|
|
140
|
+
manifest ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConnectorIcon, {
|
|
141
|
+
icon: manifest.icon,
|
|
142
|
+
accent: manifest.accent,
|
|
143
|
+
className: loop.enabled ? "size-8" : "size-8 opacity-50"
|
|
144
|
+
}) : null,
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
|
|
146
|
+
to: "/loops/$loopId",
|
|
147
|
+
params: { loopId: loop.id },
|
|
148
|
+
className: "group min-w-0 flex-1",
|
|
149
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
150
|
+
className: "flex items-center gap-2",
|
|
151
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
152
|
+
className: "truncate text-sm font-medium group-hover:underline",
|
|
153
|
+
children: loop.name
|
|
154
|
+
}), loop.enabled ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
155
|
+
variant: "secondary",
|
|
156
|
+
children: "Off"
|
|
157
|
+
})]
|
|
158
|
+
}), issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
159
|
+
className: "mt-0.5 flex items-center gap-1 truncate text-xs text-amber-700 dark:text-amber-500",
|
|
160
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleAlert, { className: "size-3 shrink-0" }), issues[0]]
|
|
161
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
162
|
+
className: "mt-0.5 truncate text-xs text-muted-foreground",
|
|
163
|
+
children: summaryOf(loop)
|
|
164
|
+
})]
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
|
|
167
|
+
checked: loop.enabled,
|
|
168
|
+
"aria-label": loop.enabled ? "Turn off" : "Turn on",
|
|
169
|
+
disabled: busy,
|
|
170
|
+
onCheckedChange: (enabled) => run(() => toggleLoop({ data: {
|
|
171
|
+
id: loop.id,
|
|
172
|
+
enabled
|
|
173
|
+
} }), enabled ? `${loop.name} is on` : `${loop.name} is off`)
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuTrigger, {
|
|
176
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
|
|
177
|
+
variant: "ghost",
|
|
178
|
+
size: "icon-sm",
|
|
179
|
+
"aria-label": "More",
|
|
180
|
+
disabled: busy
|
|
181
|
+
}),
|
|
182
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ellipsis, {})
|
|
183
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuContent, {
|
|
184
|
+
align: "end",
|
|
185
|
+
children: [
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
|
|
187
|
+
disabled: first,
|
|
188
|
+
onClick: () => run(() => reorderLoop({ data: {
|
|
189
|
+
id: loop.id,
|
|
190
|
+
direction: "up"
|
|
191
|
+
} })),
|
|
192
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowUp, {}), "Move up"]
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
|
|
195
|
+
disabled: last,
|
|
196
|
+
onClick: () => run(() => reorderLoop({ data: {
|
|
197
|
+
id: loop.id,
|
|
198
|
+
direction: "down"
|
|
199
|
+
} })),
|
|
200
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowDown, {}), "Move down"]
|
|
201
|
+
}),
|
|
202
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DropdownMenuSeparator, {}),
|
|
203
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
|
|
204
|
+
variant: "destructive",
|
|
205
|
+
onClick: () => {
|
|
206
|
+
if (!window.confirm(`Delete ${loop.name}? Its runs are deleted with it.`)) return;
|
|
207
|
+
run(() => removeLoop({ data: { id: loop.id } }), `Deleted ${loop.name}`);
|
|
208
|
+
},
|
|
209
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, {}), "Delete"]
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
})] })
|
|
213
|
+
]
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { LoopsPage as component };
|