@burdenoff/fluidgrids-fe-sdk 2026.1021.8 → 2026.1021.9
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/dist/{ExecutionDetailPage-2CAK5J7R.js → ExecutionDetailPage-CEXLJ6GN.js} +1 -1
- package/dist/{WorkflowBuilder-FDRHB5ZZ.js → WorkflowBuilder-PYJ5T23Q.js} +2 -2
- package/dist/{chunk-FZONRYII.js → chunk-2CDLVDSU.js} +5 -1
- package/dist/{chunk-2TK2KCRD.js → chunk-2QROSNUI.js} +1 -1
- package/dist/{chunk-66FIPZKK.js → chunk-RFG3PJX3.js} +1 -1
- package/dist/{chunk-VIX4NT5F.js → chunk-V2UCWUBQ.js} +1 -1
- package/dist/{chunk-WUA2WMJH.js → chunk-XOUL22OI.js} +575 -7
- package/dist/executions.js +1 -1
- package/dist/index.d.ts +104 -23
- package/dist/index.js +84 -13
- package/dist/templates.js +2 -2
- package/dist/workflow-builder.js +3 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { WorkflowBuilder } from './chunk-
|
|
1
|
+
export { WorkflowBuilder } from './chunk-RFG3PJX3.js';
|
|
2
2
|
import './chunk-6N27YGMB.js';
|
|
3
|
-
import './chunk-
|
|
3
|
+
import './chunk-2CDLVDSU.js';
|
|
4
4
|
import './chunk-GEMNVFPK.js';
|
|
5
5
|
import './chunk-IEVSFCFS.js';
|
|
6
6
|
import './chunk-7LSGGJ7D.js';
|
|
@@ -3587,7 +3587,11 @@ function isDynamicSpec(spec) {
|
|
|
3587
3587
|
var DEFAULT_HANDLES = [{ id: "source", label: "" }];
|
|
3588
3588
|
function resolveOutputHandles(data) {
|
|
3589
3589
|
const nodeData = data?.nodeData;
|
|
3590
|
-
const
|
|
3590
|
+
const operationsBlob = Array.isArray(nodeData?.operations) ? void 0 : nodeData?.operations;
|
|
3591
|
+
const operationList = Array.isArray(nodeData?.operations) ? nodeData.operations : operationsBlob?.operations ?? [];
|
|
3592
|
+
const operationName = typeof data?.operation_name === "string" ? data.operation_name : void 0;
|
|
3593
|
+
const operationSpec = operationName ? operationList.find((op) => op?.name === operationName)?.outputHandles : void 0;
|
|
3594
|
+
const spec = operationSpec ?? nodeData?.outputHandles ?? operationsBlob?.outputHandles;
|
|
3591
3595
|
if (!spec) return DEFAULT_HANDLES;
|
|
3592
3596
|
if (isDynamicSpec(spec)) {
|
|
3593
3597
|
const args = data?.args;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useImportWorkflowTemplate, FlowBuilderPage, useWorkflowTemplates, TemplateCard } from './chunk-
|
|
1
|
+
import { useImportWorkflowTemplate, FlowBuilderPage, useWorkflowTemplates, TemplateCard } from './chunk-2CDLVDSU.js';
|
|
2
2
|
import { QuotaExhaustedDialog } from './chunk-SQFFJUAT.js';
|
|
3
3
|
import { useWorkflowDefinitions, useDuplicateWorkflow } from './chunk-O6E247J6.js';
|
|
4
4
|
import { templatesControls } from './chunk-D3UWK7GY.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isCreateIntent, decideMissingWorkflow } from './chunk-6N27YGMB.js';
|
|
2
|
-
import { useInstalledNodes, buildNodeCheck, refetchStoreNodes, FlowBuilderPage, buildRequiredNodeTypes, mergeStickyNotes } from './chunk-
|
|
2
|
+
import { useInstalledNodes, buildNodeCheck, refetchStoreNodes, FlowBuilderPage, buildRequiredNodeTypes, mergeStickyNotes } from './chunk-2CDLVDSU.js';
|
|
3
3
|
import { useLazyWorkflowDefinition, useCreateWorkflow } from './chunk-O6E247J6.js';
|
|
4
4
|
import { useFlowStore } from './chunk-EGYJKHAL.js';
|
|
5
5
|
import { useTr } from './chunk-UULVIT25.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FluidGridsProvider, Card, FluidGridsMark } from './chunk-
|
|
1
|
+
import { FluidGridsProvider, Card, FluidGridsMark } from './chunk-2CDLVDSU.js';
|
|
2
2
|
import { Input } from './chunk-IEVSFCFS.js';
|
|
3
3
|
import { Button } from './chunk-GDAFKRQO.js';
|
|
4
4
|
import { cn } from './chunk-JL7CASN5.js';
|
|
@@ -4,15 +4,16 @@ import { getFluidGridsConfig } from './chunk-UZG3ZIQM.js';
|
|
|
4
4
|
import { RunLogsDocument, RunLogAppendedDocument } from './chunk-L2UWPFJM.js';
|
|
5
5
|
import { throwIfResultError } from './chunk-CFCUM4CN.js';
|
|
6
6
|
import { useTr } from './chunk-UULVIT25.js';
|
|
7
|
-
import { getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
|
|
8
|
-
import { useState,
|
|
7
|
+
import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
|
|
8
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
9
9
|
import { useParams, useNavigate, Link } from 'react-router';
|
|
10
10
|
import { Icon } from '@iconify/react';
|
|
11
11
|
import { toast } from 'sonner';
|
|
12
12
|
import { Button } from '@burdenoff/fe-libs/button';
|
|
13
|
-
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '@burdenoff/fe-libs/ui';
|
|
13
|
+
import { Card, CardHeader, Badge, CardContent, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, Input } from '@burdenoff/fe-libs/ui';
|
|
14
14
|
import { useActorProfiles, buildActorFallback } from '@burdenoff/fe-libs/shared';
|
|
15
|
-
import {
|
|
15
|
+
import { gql } from '@apollo/client';
|
|
16
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
16
17
|
|
|
17
18
|
var POLL_INTERVAL_MS = 2e3;
|
|
18
19
|
var PAGE_SIZE = 500;
|
|
@@ -164,6 +165,524 @@ function useRunLogs(runId, options = {}) {
|
|
|
164
165
|
refetch
|
|
165
166
|
};
|
|
166
167
|
}
|
|
168
|
+
var HUMAN_REQUEST_FIELDS = gql`
|
|
169
|
+
fragment WorkflowHumanRequestFields on WorkflowHumanRequest {
|
|
170
|
+
id
|
|
171
|
+
runId
|
|
172
|
+
nodeId
|
|
173
|
+
workflowKey
|
|
174
|
+
channel
|
|
175
|
+
recipient
|
|
176
|
+
responseType
|
|
177
|
+
title
|
|
178
|
+
message
|
|
179
|
+
approvalType
|
|
180
|
+
approveLabel
|
|
181
|
+
declineLabel
|
|
182
|
+
submitLabel
|
|
183
|
+
fields {
|
|
184
|
+
name
|
|
185
|
+
label
|
|
186
|
+
type
|
|
187
|
+
required
|
|
188
|
+
placeholder
|
|
189
|
+
options
|
|
190
|
+
}
|
|
191
|
+
status
|
|
192
|
+
decision
|
|
193
|
+
response
|
|
194
|
+
respondedBy
|
|
195
|
+
respondedVia
|
|
196
|
+
expiresAt
|
|
197
|
+
respondedAt
|
|
198
|
+
createdAt
|
|
199
|
+
canRespond
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
var GET_PENDING_HUMAN_REQUESTS = gql`
|
|
203
|
+
${HUMAN_REQUEST_FIELDS}
|
|
204
|
+
query GetPendingWorkflowHumanRequests($limit: Int, $offset: Int) {
|
|
205
|
+
pendingWorkflowHumanRequests(limit: $limit, offset: $offset) {
|
|
206
|
+
totalCount
|
|
207
|
+
items {
|
|
208
|
+
...WorkflowHumanRequestFields
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
var GET_RUN_HUMAN_REQUESTS = gql`
|
|
214
|
+
${HUMAN_REQUEST_FIELDS}
|
|
215
|
+
query GetWorkflowHumanRequests($runId: ID!) {
|
|
216
|
+
workflowHumanRequests(runId: $runId) {
|
|
217
|
+
...WorkflowHumanRequestFields
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
`;
|
|
221
|
+
var RESPOND_TO_HUMAN_REQUEST = gql`
|
|
222
|
+
${HUMAN_REQUEST_FIELDS}
|
|
223
|
+
mutation RespondToWorkflowHumanRequest(
|
|
224
|
+
$input: RespondToWorkflowHumanRequestInput!
|
|
225
|
+
) {
|
|
226
|
+
respondToWorkflowHumanRequest(input: $input) {
|
|
227
|
+
success
|
|
228
|
+
decision
|
|
229
|
+
error
|
|
230
|
+
request {
|
|
231
|
+
...WorkflowHumanRequestFields
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
function usePendingHumanRequests(options) {
|
|
237
|
+
const limit = options?.limit ?? 50;
|
|
238
|
+
const offset = options?.offset ?? 0;
|
|
239
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 15e3;
|
|
240
|
+
const [items, setItems] = useState([]);
|
|
241
|
+
const [totalCount, setTotalCount] = useState(0);
|
|
242
|
+
const [loading, setLoading] = useState(true);
|
|
243
|
+
const [error, setError] = useState(null);
|
|
244
|
+
const generation = useRef(0);
|
|
245
|
+
const fetchData = useCallback(async () => {
|
|
246
|
+
const ticket = ++generation.current;
|
|
247
|
+
setError(null);
|
|
248
|
+
try {
|
|
249
|
+
const result = throwIfResultError(
|
|
250
|
+
await getFluidGridsClient().query({
|
|
251
|
+
query: GET_PENDING_HUMAN_REQUESTS,
|
|
252
|
+
variables: { limit, offset },
|
|
253
|
+
fetchPolicy: "network-only"
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
if (ticket !== generation.current) return;
|
|
257
|
+
setItems(result.data?.pendingWorkflowHumanRequests?.items ?? []);
|
|
258
|
+
setTotalCount(result.data?.pendingWorkflowHumanRequests?.totalCount ?? 0);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (ticket !== generation.current) return;
|
|
261
|
+
setError(err);
|
|
262
|
+
} finally {
|
|
263
|
+
if (ticket === generation.current) setLoading(false);
|
|
264
|
+
}
|
|
265
|
+
}, [limit, offset]);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
fetchData();
|
|
268
|
+
if (pollIntervalMs <= 0) return;
|
|
269
|
+
const interval = setInterval(fetchData, pollIntervalMs);
|
|
270
|
+
return () => clearInterval(interval);
|
|
271
|
+
}, [fetchData, pollIntervalMs]);
|
|
272
|
+
return { items, totalCount, loading, error, refetch: fetchData };
|
|
273
|
+
}
|
|
274
|
+
function useRunHumanRequests(runId, options) {
|
|
275
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 0;
|
|
276
|
+
const [items, setItems] = useState([]);
|
|
277
|
+
const [error, setError] = useState(null);
|
|
278
|
+
const [loaded, setLoaded] = useState(false);
|
|
279
|
+
const [loadedAt, setLoadedAt] = useState(null);
|
|
280
|
+
const currentRunId = useRef(runId);
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
currentRunId.current = runId;
|
|
283
|
+
setItems([]);
|
|
284
|
+
setError(null);
|
|
285
|
+
setLoaded(false);
|
|
286
|
+
setLoadedAt(null);
|
|
287
|
+
}, [runId]);
|
|
288
|
+
const generation = useRef(0);
|
|
289
|
+
const fetchData = useCallback(async () => {
|
|
290
|
+
if (!runId) return;
|
|
291
|
+
const ticket = ++generation.current;
|
|
292
|
+
const startedAt = Date.now();
|
|
293
|
+
setError(null);
|
|
294
|
+
try {
|
|
295
|
+
const result = throwIfResultError(
|
|
296
|
+
await getFluidGridsClient().query({
|
|
297
|
+
query: GET_RUN_HUMAN_REQUESTS,
|
|
298
|
+
variables: { runId },
|
|
299
|
+
fetchPolicy: "network-only",
|
|
300
|
+
// A fresh request every time: joining a read already in flight would hand back a list
|
|
301
|
+
// fetched before the caller's state change, stamped as if it were newer.
|
|
302
|
+
context: { queryDeduplication: false }
|
|
303
|
+
})
|
|
304
|
+
);
|
|
305
|
+
if (ticket !== generation.current || currentRunId.current !== runId) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
setItems(result.data?.workflowHumanRequests ?? []);
|
|
309
|
+
setLoaded(true);
|
|
310
|
+
setLoadedAt(startedAt);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
if (ticket !== generation.current || currentRunId.current !== runId) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
setError(err);
|
|
316
|
+
}
|
|
317
|
+
}, [runId]);
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
fetchData();
|
|
320
|
+
if (pollIntervalMs <= 0) return;
|
|
321
|
+
const interval = setInterval(fetchData, pollIntervalMs);
|
|
322
|
+
return () => clearInterval(interval);
|
|
323
|
+
}, [fetchData, pollIntervalMs]);
|
|
324
|
+
return { items, error, loaded, loadedAt, refetch: fetchData };
|
|
325
|
+
}
|
|
326
|
+
function useRespondToHumanRequest() {
|
|
327
|
+
const [loading, setLoading] = useState(false);
|
|
328
|
+
const respond = useCallback(
|
|
329
|
+
async (id, answer) => {
|
|
330
|
+
setLoading(true);
|
|
331
|
+
try {
|
|
332
|
+
const result = throwIfResultError(
|
|
333
|
+
await getFluidGridsClient().mutate({
|
|
334
|
+
mutation: RESPOND_TO_HUMAN_REQUEST,
|
|
335
|
+
variables: { input: { id, ...answer } }
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
const payload = result.data?.respondToWorkflowHumanRequest;
|
|
339
|
+
if (!payload?.success) {
|
|
340
|
+
throw new Error(payload?.error || "The response was not recorded");
|
|
341
|
+
}
|
|
342
|
+
return payload.decision;
|
|
343
|
+
} finally {
|
|
344
|
+
setLoading(false);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
[]
|
|
348
|
+
);
|
|
349
|
+
return { respond, loading };
|
|
350
|
+
}
|
|
351
|
+
var CHANNEL_ICON = {
|
|
352
|
+
EMAIL: "mdi:email-outline",
|
|
353
|
+
SLACK: "mdi:slack",
|
|
354
|
+
IN_APP: "mdi:bell-outline"
|
|
355
|
+
};
|
|
356
|
+
function channelLabel(channel, tr) {
|
|
357
|
+
switch (channel) {
|
|
358
|
+
case "EMAIL":
|
|
359
|
+
return tr("fluidgrids.humanResponse.channelEmail", "email");
|
|
360
|
+
case "SLACK":
|
|
361
|
+
return tr("fluidgrids.humanResponse.channelSlack", "Slack");
|
|
362
|
+
default:
|
|
363
|
+
return tr("fluidgrids.humanResponse.channelInApp", "in-app");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function inputType(field) {
|
|
367
|
+
const type = (field.type || "text").toLowerCase();
|
|
368
|
+
return ["email", "number", "date", "tel", "url"].includes(type) ? type : "text";
|
|
369
|
+
}
|
|
370
|
+
function FormControls({
|
|
371
|
+
requestId,
|
|
372
|
+
fields,
|
|
373
|
+
values,
|
|
374
|
+
onChange
|
|
375
|
+
}) {
|
|
376
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: fields.map((field) => {
|
|
377
|
+
const id = `human-field-${requestId}-${field.name}`;
|
|
378
|
+
const label = `${field.label || field.name}${field.required ? " *" : ""}`;
|
|
379
|
+
const type = (field.type || "text").toLowerCase();
|
|
380
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
381
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: id, children: label }),
|
|
382
|
+
type === "textarea" ? /* @__PURE__ */ jsx(
|
|
383
|
+
Textarea,
|
|
384
|
+
{
|
|
385
|
+
id,
|
|
386
|
+
value: values[field.name] ?? "",
|
|
387
|
+
placeholder: field.placeholder ?? void 0,
|
|
388
|
+
onChange: (event) => onChange(field.name, event.target.value)
|
|
389
|
+
}
|
|
390
|
+
) : type === "dropdown" || type === "select" ? /* @__PURE__ */ jsxs(
|
|
391
|
+
"select",
|
|
392
|
+
{
|
|
393
|
+
id,
|
|
394
|
+
className: "w-full rounded-md border border-border-secondary bg-background-primary px-3 py-2 text-sm text-text-primary",
|
|
395
|
+
value: values[field.name] ?? "",
|
|
396
|
+
onChange: (event) => onChange(field.name, event.target.value),
|
|
397
|
+
children: [
|
|
398
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
|
|
399
|
+
(field.options ?? []).map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
) : /* @__PURE__ */ jsx(
|
|
403
|
+
Input,
|
|
404
|
+
{
|
|
405
|
+
id,
|
|
406
|
+
type: inputType(field),
|
|
407
|
+
value: values[field.name] ?? "",
|
|
408
|
+
placeholder: field.placeholder ?? void 0,
|
|
409
|
+
onChange: (event) => onChange(field.name, event.target.value)
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
] }, field.name);
|
|
413
|
+
}) });
|
|
414
|
+
}
|
|
415
|
+
function HumanRequestCard({
|
|
416
|
+
request,
|
|
417
|
+
onAnswered
|
|
418
|
+
}) {
|
|
419
|
+
const tr = useTr();
|
|
420
|
+
const { respond, loading } = useRespondToHumanRequest();
|
|
421
|
+
const [text, setText] = useState("");
|
|
422
|
+
const [values, setValues] = useState({});
|
|
423
|
+
const submit = async (answer) => {
|
|
424
|
+
try {
|
|
425
|
+
const decision = await respond(request.id, answer);
|
|
426
|
+
toast.success(
|
|
427
|
+
decision === "declined" ? tr(
|
|
428
|
+
"fluidgrids.humanResponse.declinedToast",
|
|
429
|
+
"Declined \u2014 the workflow is continuing"
|
|
430
|
+
) : tr(
|
|
431
|
+
"fluidgrids.humanResponse.answeredToast",
|
|
432
|
+
"Response sent \u2014 the workflow is continuing"
|
|
433
|
+
)
|
|
434
|
+
);
|
|
435
|
+
onAnswered();
|
|
436
|
+
} catch (err) {
|
|
437
|
+
toast.error(
|
|
438
|
+
err instanceof Error ? err.message : tr(
|
|
439
|
+
"fluidgrids.humanResponse.failed",
|
|
440
|
+
"Your response could not be recorded"
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const missingRequired = request.responseType === "CUSTOM_FORM" && request.fields.some(
|
|
446
|
+
(field) => field.required && !(values[field.name] ?? "").trim()
|
|
447
|
+
);
|
|
448
|
+
const emptyText = request.responseType === "FREE_TEXT" && !text.trim();
|
|
449
|
+
const recipient = request.channel === "IN_APP" && !request.recipient ? tr("fluidgrids.humanResponse.anyMember", "any workspace member") : request.recipient;
|
|
450
|
+
return /* @__PURE__ */ jsxs(
|
|
451
|
+
Card,
|
|
452
|
+
{
|
|
453
|
+
className: "border border-border-secondary bg-background-secondary",
|
|
454
|
+
"data-testid": "human-request-card",
|
|
455
|
+
"data-request-id": request.id,
|
|
456
|
+
children: [
|
|
457
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "pb-3", children: [
|
|
458
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
459
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
460
|
+
/* @__PURE__ */ jsx(
|
|
461
|
+
Icon,
|
|
462
|
+
{
|
|
463
|
+
icon: CHANNEL_ICON[request.channel] ?? "mdi:account-question",
|
|
464
|
+
width: 18,
|
|
465
|
+
className: "shrink-0 text-text-secondary"
|
|
466
|
+
}
|
|
467
|
+
),
|
|
468
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-text-primary", children: request.title })
|
|
469
|
+
] }),
|
|
470
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: request.workflowKey })
|
|
471
|
+
] }),
|
|
472
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-text-secondary", children: [
|
|
473
|
+
tr(
|
|
474
|
+
"fluidgrids.humanResponse.askedVia",
|
|
475
|
+
"Asked {{recipient}} via {{channel}}",
|
|
476
|
+
{
|
|
477
|
+
recipient: recipient ?? "",
|
|
478
|
+
channel: channelLabel(request.channel, tr)
|
|
479
|
+
}
|
|
480
|
+
),
|
|
481
|
+
request.expiresAt && ` \xB7 ${tr("fluidgrids.humanResponse.until", "until {{time}}", {
|
|
482
|
+
time: new Date(request.expiresAt).toLocaleString()
|
|
483
|
+
})}`
|
|
484
|
+
] })
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-3", children: [
|
|
487
|
+
request.message && /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words text-sm text-text-primary", children: request.message }),
|
|
488
|
+
!request.canRespond && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: request.channel === "IN_APP" ? tr(
|
|
489
|
+
"fluidgrids.humanResponse.notForYou",
|
|
490
|
+
"This request was sent to someone else."
|
|
491
|
+
) : tr(
|
|
492
|
+
"fluidgrids.humanResponse.answerByLink",
|
|
493
|
+
"Answered through the link in the {{channel}} message.",
|
|
494
|
+
{ channel: channelLabel(request.channel, tr) }
|
|
495
|
+
) }),
|
|
496
|
+
request.canRespond && request.responseType === "FREE_TEXT" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
497
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `human-text-${request.id}`, children: tr("fluidgrids.humanResponse.yourResponse", "Your response") }),
|
|
498
|
+
/* @__PURE__ */ jsx(
|
|
499
|
+
Textarea,
|
|
500
|
+
{
|
|
501
|
+
id: `human-text-${request.id}`,
|
|
502
|
+
value: text,
|
|
503
|
+
onChange: (event) => setText(event.target.value)
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
] }),
|
|
507
|
+
request.canRespond && request.responseType === "CUSTOM_FORM" && /* @__PURE__ */ jsx(
|
|
508
|
+
FormControls,
|
|
509
|
+
{
|
|
510
|
+
requestId: request.id,
|
|
511
|
+
fields: request.fields,
|
|
512
|
+
values,
|
|
513
|
+
onChange: (name, value) => setValues((current) => ({ ...current, [name]: value }))
|
|
514
|
+
}
|
|
515
|
+
),
|
|
516
|
+
request.canRespond && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: request.responseType === "APPROVAL" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
517
|
+
/* @__PURE__ */ jsxs(
|
|
518
|
+
Button,
|
|
519
|
+
{
|
|
520
|
+
size: "sm",
|
|
521
|
+
disabled: loading,
|
|
522
|
+
onClick: () => submit({ action: "approve" }),
|
|
523
|
+
"data-testid": "human-request-approve",
|
|
524
|
+
children: [
|
|
525
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:check", className: "mr-1.5", width: 16 }),
|
|
526
|
+
request.approveLabel
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
),
|
|
530
|
+
request.approvalType === "double" && /* @__PURE__ */ jsxs(
|
|
531
|
+
Button,
|
|
532
|
+
{
|
|
533
|
+
size: "sm",
|
|
534
|
+
variant: "destructive",
|
|
535
|
+
disabled: loading,
|
|
536
|
+
onClick: () => submit({ action: "decline" }),
|
|
537
|
+
"data-testid": "human-request-decline",
|
|
538
|
+
children: [
|
|
539
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "mr-1.5", width: 16 }),
|
|
540
|
+
request.declineLabel
|
|
541
|
+
]
|
|
542
|
+
}
|
|
543
|
+
)
|
|
544
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
545
|
+
Button,
|
|
546
|
+
{
|
|
547
|
+
size: "sm",
|
|
548
|
+
disabled: loading || missingRequired || emptyText,
|
|
549
|
+
onClick: () => submit(
|
|
550
|
+
request.responseType === "FREE_TEXT" ? { text } : { values }
|
|
551
|
+
),
|
|
552
|
+
"data-testid": "human-request-submit",
|
|
553
|
+
children: [
|
|
554
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:send", className: "mr-1.5", width: 16 }),
|
|
555
|
+
request.submitLabel
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
) })
|
|
559
|
+
] })
|
|
560
|
+
]
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/modules/human-response/utils/answer-rows.ts
|
|
566
|
+
function answerRows(request, tr) {
|
|
567
|
+
if (request.status !== "RESPONDED" || request.responseType === "APPROVAL") {
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
570
|
+
const data = request.response?.data;
|
|
571
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
572
|
+
const values = data;
|
|
573
|
+
const show = (value) => {
|
|
574
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
575
|
+
if (value === null || value === void 0) return "";
|
|
576
|
+
return String(value);
|
|
577
|
+
};
|
|
578
|
+
if (request.responseType === "FREE_TEXT") {
|
|
579
|
+
return [
|
|
580
|
+
{
|
|
581
|
+
key: "text",
|
|
582
|
+
label: tr("fluidgrids.humanResponse.answerLabel", "Response"),
|
|
583
|
+
value: show(values.text)
|
|
584
|
+
}
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
return request.fields.filter((field) => values[field.name] !== void 0).map((field) => ({
|
|
588
|
+
key: field.name,
|
|
589
|
+
label: field.label || field.name,
|
|
590
|
+
value: show(values[field.name])
|
|
591
|
+
}));
|
|
592
|
+
}
|
|
593
|
+
function outcome(request, tr) {
|
|
594
|
+
const when = request.respondedAt ? new Date(request.respondedAt).toLocaleString() : "";
|
|
595
|
+
switch (request.status) {
|
|
596
|
+
case "RESPONDED":
|
|
597
|
+
return request.decision === "declined" ? tr("fluidgrids.humanResponse.outcomeDeclined", "Declined {{when}}", {
|
|
598
|
+
when
|
|
599
|
+
}) : request.decision === "approved" ? tr(
|
|
600
|
+
"fluidgrids.humanResponse.outcomeApproved",
|
|
601
|
+
"Approved {{when}}",
|
|
602
|
+
{
|
|
603
|
+
when
|
|
604
|
+
}
|
|
605
|
+
) : tr(
|
|
606
|
+
"fluidgrids.humanResponse.outcomeResponded",
|
|
607
|
+
"Answered {{when}}",
|
|
608
|
+
{ when }
|
|
609
|
+
);
|
|
610
|
+
case "TIMED_OUT":
|
|
611
|
+
return tr(
|
|
612
|
+
"fluidgrids.humanResponse.outcomeTimedOut",
|
|
613
|
+
"Nobody answered in time \u2014 continued down Timed out"
|
|
614
|
+
);
|
|
615
|
+
case "CANCELLED":
|
|
616
|
+
return tr(
|
|
617
|
+
"fluidgrids.humanResponse.outcomeCancelled",
|
|
618
|
+
"Withdrawn (the node was retried or the run stopped)"
|
|
619
|
+
);
|
|
620
|
+
default:
|
|
621
|
+
return request.expiresAt ? tr(
|
|
622
|
+
"fluidgrids.humanResponse.waitingUntil",
|
|
623
|
+
"Waiting until {{time}}",
|
|
624
|
+
{
|
|
625
|
+
time: new Date(request.expiresAt).toLocaleString()
|
|
626
|
+
}
|
|
627
|
+
) : tr("fluidgrids.humanResponse.waiting", "Waiting for a response");
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function RunHumanRequests({ requests }) {
|
|
631
|
+
const tr = useTr();
|
|
632
|
+
if (requests.length === 0) return null;
|
|
633
|
+
return /* @__PURE__ */ jsxs(
|
|
634
|
+
"div",
|
|
635
|
+
{
|
|
636
|
+
className: "mb-6 space-y-2 rounded-lg border border-border-secondary bg-background-secondary p-4",
|
|
637
|
+
"data-testid": "run-human-requests",
|
|
638
|
+
children: [
|
|
639
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-text-primary", children: [
|
|
640
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:account-clock-outline", width: 18 }),
|
|
641
|
+
tr("fluidgrids.humanResponse.runHeading", "Waiting on people")
|
|
642
|
+
] }),
|
|
643
|
+
requests.map((request) => {
|
|
644
|
+
const answer = answerRows(request, tr);
|
|
645
|
+
const recipient = request.recipient || tr("fluidgrids.humanResponse.anyMember", "any workspace member");
|
|
646
|
+
return /* @__PURE__ */ jsxs(
|
|
647
|
+
"div",
|
|
648
|
+
{
|
|
649
|
+
className: "flex flex-col gap-0.5 text-sm sm:flex-row sm:flex-wrap sm:items-baseline sm:gap-x-2",
|
|
650
|
+
"data-testid": "run-human-request",
|
|
651
|
+
"data-status": request.status,
|
|
652
|
+
children: [
|
|
653
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-text-primary", children: request.title }),
|
|
654
|
+
/* @__PURE__ */ jsxs("span", { className: "text-text-secondary", children: [
|
|
655
|
+
tr(
|
|
656
|
+
"fluidgrids.humanResponse.askedVia",
|
|
657
|
+
"Asked {{recipient}} via {{channel}}",
|
|
658
|
+
{ recipient, channel: channelLabel(request.channel, tr) }
|
|
659
|
+
),
|
|
660
|
+
" \xB7 ",
|
|
661
|
+
outcome(request, tr)
|
|
662
|
+
] }),
|
|
663
|
+
answer.length > 0 && /* @__PURE__ */ jsx(
|
|
664
|
+
"dl",
|
|
665
|
+
{
|
|
666
|
+
className: "mt-1 w-full space-y-0.5 text-xs sm:mt-0",
|
|
667
|
+
"data-testid": "run-human-answer",
|
|
668
|
+
children: answer.map((row) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
669
|
+
/* @__PURE__ */ jsxs("dt", { className: "shrink-0 font-medium text-text-secondary", children: [
|
|
670
|
+
row.label,
|
|
671
|
+
":"
|
|
672
|
+
] }),
|
|
673
|
+
/* @__PURE__ */ jsx("dd", { className: "whitespace-pre-wrap break-words text-text-primary", children: row.value })
|
|
674
|
+
] }, row.key))
|
|
675
|
+
}
|
|
676
|
+
)
|
|
677
|
+
]
|
|
678
|
+
},
|
|
679
|
+
request.id
|
|
680
|
+
);
|
|
681
|
+
})
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
}
|
|
167
686
|
function ExecutionDetailPage() {
|
|
168
687
|
const tr = useTr();
|
|
169
688
|
const { runId } = useParams();
|
|
@@ -176,6 +695,29 @@ function ExecutionDetailPage() {
|
|
|
176
695
|
);
|
|
177
696
|
const [minLogLevel, setMinLogLevel] = useState(null);
|
|
178
697
|
const { execution, loading, error, refetch } = useExecution(runId);
|
|
698
|
+
const runCanChange = execution?.status === "RUNNING" || execution?.status === "PENDING" || execution?.status === "WAITING";
|
|
699
|
+
const {
|
|
700
|
+
items: humanRequests,
|
|
701
|
+
loadedAt: humanRequestsLoadedAt,
|
|
702
|
+
error: humanRequestsError,
|
|
703
|
+
refetch: refetchHumanRequests
|
|
704
|
+
} = useRunHumanRequests(runId, {
|
|
705
|
+
pollIntervalMs: runCanChange ? 1e4 : 0
|
|
706
|
+
});
|
|
707
|
+
const awaitingPerson = humanRequests.some(
|
|
708
|
+
(request) => request.status === "PENDING"
|
|
709
|
+
);
|
|
710
|
+
const [waitingSeenAt, setWaitingSeenAt] = useState(null);
|
|
711
|
+
const isWaiting = execution?.status === "WAITING";
|
|
712
|
+
useEffect(() => {
|
|
713
|
+
if (!isWaiting) {
|
|
714
|
+
setWaitingSeenAt(null);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
setWaitingSeenAt(Date.now());
|
|
718
|
+
void refetchHumanRequests();
|
|
719
|
+
}, [isWaiting, refetchHumanRequests]);
|
|
720
|
+
const humanRequestsCurrent = waitingSeenAt !== null && humanRequestsLoadedAt !== null && humanRequestsLoadedAt >= waitingSeenAt;
|
|
179
721
|
const nodeDisplay = useMemo(
|
|
180
722
|
() => buildNodeDisplayMap(execution?.workflow?.definitionJson),
|
|
181
723
|
[execution?.workflow?.definitionJson]
|
|
@@ -203,7 +745,7 @@ function ExecutionDetailPage() {
|
|
|
203
745
|
terminateExecution,
|
|
204
746
|
loading: actionLoading
|
|
205
747
|
} = useExecutionActions();
|
|
206
|
-
const isActive =
|
|
748
|
+
const isActive = runCanChange;
|
|
207
749
|
const isHistoricalTestRun = execution?.mode === "TEST" && (execution.status === "COMPLETED" || execution.status === "FAILED" || execution.status === "CANCELLED" || execution.status === "TERMINATED");
|
|
208
750
|
useAutoRefresh(
|
|
209
751
|
useCallback(() => {
|
|
@@ -534,7 +1076,7 @@ function ExecutionDetailPage() {
|
|
|
534
1076
|
)
|
|
535
1077
|
] }),
|
|
536
1078
|
canControlRuns && execution.status === "WAITING" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
537
|
-
/* @__PURE__ */ jsxs(
|
|
1079
|
+
humanRequestsCurrent && !awaitingPerson && /* @__PURE__ */ jsxs(
|
|
538
1080
|
Button,
|
|
539
1081
|
{
|
|
540
1082
|
variant: "outline",
|
|
@@ -550,6 +1092,31 @@ function ExecutionDetailPage() {
|
|
|
550
1092
|
]
|
|
551
1093
|
}
|
|
552
1094
|
),
|
|
1095
|
+
!humanRequestsCurrent && humanRequestsError && /* @__PURE__ */ jsxs(
|
|
1096
|
+
"span",
|
|
1097
|
+
{
|
|
1098
|
+
className: "flex items-center gap-2 text-sm text-[var(--color-status-error-text)]",
|
|
1099
|
+
"data-testid": "human-requests-error",
|
|
1100
|
+
children: [
|
|
1101
|
+
tr(
|
|
1102
|
+
"fluidgrids.humanResponse.checkFailed",
|
|
1103
|
+
"Could not check whether this run is waiting on a person."
|
|
1104
|
+
),
|
|
1105
|
+
/* @__PURE__ */ jsxs(
|
|
1106
|
+
Button,
|
|
1107
|
+
{
|
|
1108
|
+
variant: "outline",
|
|
1109
|
+
size: "sm",
|
|
1110
|
+
onClick: () => void refetchHumanRequests(),
|
|
1111
|
+
children: [
|
|
1112
|
+
/* @__PURE__ */ jsx(Icon, { icon: "mdi:refresh", className: "mr-1", width: 16 }),
|
|
1113
|
+
tr("fluidgrids.humanResponse.retry", "Retry")
|
|
1114
|
+
]
|
|
1115
|
+
}
|
|
1116
|
+
)
|
|
1117
|
+
]
|
|
1118
|
+
}
|
|
1119
|
+
),
|
|
553
1120
|
/* @__PURE__ */ jsxs(
|
|
554
1121
|
Button,
|
|
555
1122
|
{
|
|
@@ -693,6 +1260,7 @@ function ExecutionDetailPage() {
|
|
|
693
1260
|
] }),
|
|
694
1261
|
/* @__PURE__ */ jsx("p", { className: "text-[var(--color-status-error-text)]", children: execution.errorMessage })
|
|
695
1262
|
] }),
|
|
1263
|
+
/* @__PURE__ */ jsx(RunHumanRequests, { requests: humanRequests }),
|
|
696
1264
|
/* @__PURE__ */ jsx("div", { className: "border-b border-border-secondary mb-6", children: /* @__PURE__ */ jsx("nav", { className: "flex gap-4 -mb-px", children: tabs.map((tab) => /* @__PURE__ */ jsxs(
|
|
697
1265
|
"button",
|
|
698
1266
|
{
|
|
@@ -885,4 +1453,4 @@ function ExecutionDetailPage() {
|
|
|
885
1453
|
] });
|
|
886
1454
|
}
|
|
887
1455
|
|
|
888
|
-
export { ExecutionDetailPage };
|
|
1456
|
+
export { ExecutionDetailPage, HumanRequestCard, RunHumanRequests, usePendingHumanRequests, useRespondToHumanRequest, useRunHumanRequests };
|