@flowdrop/flowdrop 1.12.0 → 1.14.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/README.md +5 -0
- package/dist/components/ConfigForm.svelte +1 -0
- package/dist/components/ConfigPanel.svelte +7 -1
- package/dist/components/NodeSwapPicker.svelte +5 -1
- package/dist/components/PipelineStatus.svelte +11 -2
- package/dist/components/SchemaForm.svelte +1 -0
- package/dist/components/SettingsPanel.svelte +5 -1
- package/dist/components/WorkflowEditor.svelte +5 -1
- package/dist/components/chat/AIChatPanel.svelte +1 -5
- package/dist/components/form/FormAutocomplete.svelte +69 -15
- package/dist/components/form/FormField.svelte +21 -0
- package/dist/components/form/FormFieldLight.svelte +1 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +5 -1
- package/dist/components/interrupt/InterruptBubble.svelte +75 -17
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
- package/dist/components/playground/ChatBubble.svelte +287 -0
- package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
- package/dist/components/playground/ChatInput.svelte +11 -5
- package/dist/components/playground/ControlPanel.svelte +42 -29
- package/dist/components/playground/ExecutionConsole.svelte +5 -1
- package/dist/components/playground/ExecutionConsole.svelte.d.ts +2 -0
- package/dist/components/playground/ExecutionList.svelte +7 -2
- package/dist/components/playground/HierarchyTrail.svelte +88 -0
- package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
- package/dist/components/playground/LogRow.svelte +179 -0
- package/dist/components/playground/LogRow.svelte.d.ts +8 -0
- package/dist/components/playground/MessageBubble.stories.svelte +89 -0
- package/dist/components/playground/MessageBubble.svelte +23 -738
- package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
- package/dist/components/playground/MessageCard.svelte +107 -0
- package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
- package/dist/components/playground/MessageMarkdown.svelte +170 -0
- package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
- package/dist/components/playground/MessageNotice.svelte +121 -0
- package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
- package/dist/components/playground/MessageStream.svelte +215 -10
- package/dist/components/playground/MessageStream.svelte.d.ts +5 -0
- package/dist/components/playground/MessageTagChip.svelte +117 -0
- package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
- package/dist/components/playground/MessageTagStrip.svelte +37 -0
- package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
- package/dist/components/playground/PipelineKanbanView.svelte +40 -11
- package/dist/components/playground/PipelinePanel.svelte +5 -1
- package/dist/components/playground/PipelineTableView.svelte +20 -6
- package/dist/components/playground/Playground.svelte +84 -22
- package/dist/components/playground/PlaygroundStudio.svelte +99 -7
- package/dist/components/playground/messageDisplay.d.ts +19 -0
- package/dist/components/playground/messageDisplay.js +62 -0
- package/dist/components/playground/pipelineViewUtils.svelte.js +11 -4
- package/dist/form/autocomplete.d.ts +1 -0
- package/dist/form/autocomplete.js +1 -0
- package/dist/form/index.d.ts +17 -0
- package/dist/form/index.js +19 -0
- package/dist/messages/defaults.d.ts +5 -0
- package/dist/messages/defaults.js +6 -0
- package/dist/openapi/v1/openapi.yaml +6403 -0
- package/dist/schemas/v1/workflow.schema.json +46 -1
- package/dist/services/categoriesApi.d.ts +2 -1
- package/dist/services/categoriesApi.js +5 -3
- package/dist/services/playgroundService.d.ts +23 -4
- package/dist/services/playgroundService.js +22 -9
- package/dist/services/portConfigApi.d.ts +2 -1
- package/dist/services/portConfigApi.js +5 -3
- package/dist/stores/playgroundStore.svelte.d.ts +22 -1
- package/dist/stores/playgroundStore.svelte.js +109 -32
- package/dist/svelte-app.d.ts +1 -0
- package/dist/svelte-app.js +5 -5
- package/dist/types/index.d.ts +13 -0
- package/dist/types/playground.d.ts +112 -2
- package/dist/types/playground.js +14 -0
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -158,6 +158,11 @@ FlowDrop provides tree-shakeable sub-module exports so you can import only what
|
|
|
158
158
|
| `@flowdrop/flowdrop/settings` | SettingsPanel, stores, services |
|
|
159
159
|
| `@flowdrop/flowdrop/styles` | Base CSS stylesheet |
|
|
160
160
|
| `@flowdrop/flowdrop/schema` | Workflow JSON schema |
|
|
161
|
+
| `@flowdrop/flowdrop/openapi` | OpenAPI spec (YAML) for the FlowDrop backend API |
|
|
162
|
+
|
|
163
|
+
### OpenAPI spec
|
|
164
|
+
|
|
165
|
+
The full OpenAPI spec for the FlowDrop backend API ships with the package, version-matched to your installed release. It defines the node-config / form-element schema (`ConfigProperty`), playground messages, and every endpoint. Resolve it from `@flowdrop/flowdrop/openapi`, or read it directly at `node_modules/@flowdrop/flowdrop/dist/openapi/v1/openapi.yaml`. Point your AI assistant at that file when authoring node config schemas. The latest spec is also browsable at [api.flowdrop.io](https://api.flowdrop.io).
|
|
161
166
|
|
|
162
167
|
## Integration
|
|
163
168
|
|
|
@@ -191,6 +191,7 @@
|
|
|
191
191
|
* This fixes the Svelte 5 reactivity warnings
|
|
192
192
|
*/
|
|
193
193
|
let configValues = $state<Record<string, unknown>>({});
|
|
194
|
+
setContext<() => Record<string, unknown>>('flowdrop:getFormValues', () => configValues);
|
|
194
195
|
|
|
195
196
|
/**
|
|
196
197
|
* UI Extension values for display settings
|
|
@@ -77,7 +77,13 @@
|
|
|
77
77
|
<Icon icon="heroicons:arrows-right-left" />
|
|
78
78
|
</button>
|
|
79
79
|
{/if}
|
|
80
|
-
<button
|
|
80
|
+
<button
|
|
81
|
+
class="config-panel__close"
|
|
82
|
+
onclick={onClose}
|
|
83
|
+
aria-label={m().layout.closeConfigPanel}
|
|
84
|
+
>
|
|
85
|
+
×
|
|
86
|
+
</button>
|
|
81
87
|
</div>
|
|
82
88
|
</div>
|
|
83
89
|
|
|
@@ -81,7 +81,11 @@
|
|
|
81
81
|
<div class="swap-picker">
|
|
82
82
|
<!-- Header -->
|
|
83
83
|
<div class="swap-picker__header">
|
|
84
|
-
<button
|
|
84
|
+
<button
|
|
85
|
+
class="swap-picker__back"
|
|
86
|
+
onclick={onCancel}
|
|
87
|
+
aria-label={m().layout.backToConfiguration}
|
|
88
|
+
>
|
|
85
89
|
<Icon icon="heroicons:arrow-left" />
|
|
86
90
|
</button>
|
|
87
91
|
<h2 class="swap-picker__title">Swap Node</h2>
|
|
@@ -37,8 +37,17 @@
|
|
|
37
37
|
) => void;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
let {
|
|
41
|
-
|
|
40
|
+
let {
|
|
41
|
+
pipelineId,
|
|
42
|
+
workflow,
|
|
43
|
+
apiClient,
|
|
44
|
+
baseUrl,
|
|
45
|
+
endpointConfig,
|
|
46
|
+
onActionsReady,
|
|
47
|
+
runLabel,
|
|
48
|
+
isEmbedded = false,
|
|
49
|
+
refreshTrigger = 0
|
|
50
|
+
}: Props = $props();
|
|
42
51
|
|
|
43
52
|
// Track previous trigger value so the $effect only fires on increments, not on initial mount.
|
|
44
53
|
// svelte-ignore state_referenced_locally
|
|
@@ -197,6 +197,7 @@
|
|
|
197
197
|
* Internal reactive state for form values
|
|
198
198
|
*/
|
|
199
199
|
let formValues = $state<Record<string, unknown>>({});
|
|
200
|
+
setContext<() => Record<string, unknown>>('flowdrop:getFormValues', () => formValues);
|
|
200
201
|
|
|
201
202
|
/**
|
|
202
203
|
* Initialize form values when schema or values change
|
|
@@ -362,7 +362,11 @@
|
|
|
362
362
|
|
|
363
363
|
<div class="flowdrop-settings-panel {className}">
|
|
364
364
|
<!-- Tab Navigation -->
|
|
365
|
-
<div
|
|
365
|
+
<div
|
|
366
|
+
class="flowdrop-settings-panel__tabs"
|
|
367
|
+
role="tablist"
|
|
368
|
+
aria-label={m().layout.settingsCategories}
|
|
369
|
+
>
|
|
366
370
|
{#each categories as category, index (category)}
|
|
367
371
|
<button
|
|
368
372
|
class="flowdrop-settings-panel__tab"
|
|
@@ -277,7 +277,11 @@
|
|
|
277
277
|
const rawStatus = statuses[node.id];
|
|
278
278
|
if (!rawStatus) return node;
|
|
279
279
|
|
|
280
|
-
const existing = node.data.executionInfo ?? {
|
|
280
|
+
const existing = node.data.executionInfo ?? {
|
|
281
|
+
status: 'idle' as const,
|
|
282
|
+
executionCount: 0,
|
|
283
|
+
isExecuting: false
|
|
284
|
+
};
|
|
281
285
|
return {
|
|
282
286
|
...node,
|
|
283
287
|
data: {
|
|
@@ -288,11 +288,7 @@
|
|
|
288
288
|
return;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
if (
|
|
292
|
-
getBehaviorSettings().chatAutoRetry &&
|
|
293
|
-
workflowId &&
|
|
294
|
-
autoRetryCount < MAX_AUTO_RETRIES
|
|
295
|
-
) {
|
|
291
|
+
if (getBehaviorSettings().chatAutoRetry && workflowId && autoRetryCount < MAX_AUTO_RETRIES) {
|
|
296
292
|
autoRetryCount++;
|
|
297
293
|
const errorText = buildBatchErrorMessage(
|
|
298
294
|
completedCount,
|
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
'flowdrop:getAuthProvider'
|
|
69
69
|
);
|
|
70
70
|
const getBaseUrl = getContext<(() => string) | undefined>('flowdrop:getBaseUrl');
|
|
71
|
+
const getFormValues = getContext<(() => Record<string, unknown>) | undefined>(
|
|
72
|
+
'flowdrop:getFormValues'
|
|
73
|
+
);
|
|
71
74
|
|
|
72
75
|
// Hoist the autocomplete branch — seven reads in the template, one inside
|
|
73
76
|
// an {#each tag} loop. One getter walk per render.
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
const valueField = $derived(autocomplete.valueField ?? 'value');
|
|
83
86
|
const allowFreeText = $derived(autocomplete.allowFreeText ?? false);
|
|
84
87
|
const multiple = $derived(autocomplete.multiple ?? false);
|
|
88
|
+
const params = $derived(autocomplete.params ?? {});
|
|
85
89
|
|
|
86
90
|
// Component state
|
|
87
91
|
let inputElement: HTMLInputElement | undefined = $state(undefined);
|
|
@@ -102,6 +106,34 @@
|
|
|
102
106
|
// Cache of value-to-label mappings for selected items
|
|
103
107
|
let labelCache = $state<Map<string, string>>(new Map());
|
|
104
108
|
|
|
109
|
+
// Resolved values for each param dependency: { paramName -> currentFieldValue }
|
|
110
|
+
// Used both in buildUrl (appending to query) and in the dep-change effect.
|
|
111
|
+
const depParamValues = $derived.by(() => {
|
|
112
|
+
const all = getFormValues?.() ?? {};
|
|
113
|
+
const result: Record<string, string> = {};
|
|
114
|
+
for (const [paramName, fieldName] of Object.entries(params)) {
|
|
115
|
+
const val = all[fieldName];
|
|
116
|
+
if (val !== undefined && val !== '') result[paramName] = String(val);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Stable fingerprint — any change triggers selection clearing.
|
|
122
|
+
// JSON.stringify gives a canonical string without null-byte ambiguity.
|
|
123
|
+
const depFingerprint = $derived(JSON.stringify(depParamValues));
|
|
124
|
+
// svelte-ignore state_referenced_locally — intentional initial snapshot; the effect below tracks subsequent changes
|
|
125
|
+
let prevDepFingerprint = depFingerprint;
|
|
126
|
+
|
|
127
|
+
$effect(() => {
|
|
128
|
+
if (Object.keys(params).length > 0 && !getFormValues) {
|
|
129
|
+
logger.warn(
|
|
130
|
+
'[FormAutocomplete] `params` is configured but no flowdrop:getFormValues context ' +
|
|
131
|
+
'was found. Dependent field values will not be passed to the autocomplete URL. ' +
|
|
132
|
+
'Ensure this component is rendered inside ConfigForm or SchemaForm.'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
105
137
|
// Generate unique IDs for accessibility
|
|
106
138
|
// svelte-ignore state_referenced_locally — id prop never changes
|
|
107
139
|
const listboxId = `${id}-listbox`;
|
|
@@ -160,7 +192,13 @@
|
|
|
160
192
|
: `${baseUrl}${autocomplete.url}`;
|
|
161
193
|
|
|
162
194
|
const separator = url.includes('?') ? '&' : '?';
|
|
163
|
-
|
|
195
|
+
let fullUrl = `${url}${separator}${encodeURIComponent(queryParam)}=${encodeURIComponent(query)}`;
|
|
196
|
+
|
|
197
|
+
for (const [paramName, val] of Object.entries(depParamValues)) {
|
|
198
|
+
fullUrl += `&${encodeURIComponent(paramName)}=${encodeURIComponent(val)}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return fullUrl;
|
|
164
202
|
}
|
|
165
203
|
|
|
166
204
|
/**
|
|
@@ -198,25 +236,25 @@
|
|
|
198
236
|
|
|
199
237
|
isLoading = true;
|
|
200
238
|
error = null;
|
|
201
|
-
|
|
239
|
+
// Capture in a local const so the timeout closure and fetch signal
|
|
240
|
+
// always reference the same controller, even if a new request starts
|
|
241
|
+
// before the 5-second timeout fires.
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
abortController = controller;
|
|
202
244
|
|
|
245
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
203
246
|
try {
|
|
204
247
|
// Build headers with authentication (call getter to get current value)
|
|
205
248
|
const headers = await buildFetchHeaders(getAuthProvider?.());
|
|
206
249
|
|
|
207
|
-
|
|
208
|
-
const timeoutId = setTimeout(() => {
|
|
209
|
-
abortController?.abort();
|
|
210
|
-
}, 5000);
|
|
250
|
+
timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
211
251
|
|
|
212
252
|
const response = await fetch(buildUrl(query), {
|
|
213
253
|
method: 'GET',
|
|
214
254
|
headers,
|
|
215
|
-
signal:
|
|
255
|
+
signal: controller.signal
|
|
216
256
|
});
|
|
217
257
|
|
|
218
|
-
clearTimeout(timeoutId);
|
|
219
|
-
|
|
220
258
|
if (!response.ok) {
|
|
221
259
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
222
260
|
}
|
|
@@ -240,6 +278,7 @@
|
|
|
240
278
|
error = err instanceof Error ? err.message : 'Failed to fetch suggestions';
|
|
241
279
|
suggestions = [];
|
|
242
280
|
} finally {
|
|
281
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
243
282
|
isLoading = false;
|
|
244
283
|
abortController = null;
|
|
245
284
|
}
|
|
@@ -297,6 +336,13 @@
|
|
|
297
336
|
setTimeout(() => {
|
|
298
337
|
hideDropdown();
|
|
299
338
|
|
|
339
|
+
// When dependent params are configured, the suggestion list is bound to the
|
|
340
|
+
// current values of sibling fields. Clear it on blur so that fetchOnFocus
|
|
341
|
+
// always re-fetches with the current dep values rather than showing stale results.
|
|
342
|
+
if (Object.keys(params).length > 0) {
|
|
343
|
+
suggestions = [];
|
|
344
|
+
}
|
|
345
|
+
|
|
300
346
|
// If not allowFreeText and single mode, validate the input
|
|
301
347
|
if (!allowFreeText && !multiple && inputValue !== '') {
|
|
302
348
|
const currentVal = selectedValues;
|
|
@@ -542,6 +588,18 @@
|
|
|
542
588
|
}
|
|
543
589
|
});
|
|
544
590
|
|
|
591
|
+
$effect(() => {
|
|
592
|
+
const current = depFingerprint;
|
|
593
|
+
if (current === prevDepFingerprint) return;
|
|
594
|
+
prevDepFingerprint = current;
|
|
595
|
+
// A dependency field changed — abort any in-flight fetch, clear state
|
|
596
|
+
abortController?.abort();
|
|
597
|
+
suggestions = [];
|
|
598
|
+
labelCache = new Map();
|
|
599
|
+
if (multiple) onChange([]);
|
|
600
|
+
else onChange('');
|
|
601
|
+
});
|
|
602
|
+
|
|
545
603
|
/**
|
|
546
604
|
* Cleanup on unmount
|
|
547
605
|
*/
|
|
@@ -567,11 +625,11 @@
|
|
|
567
625
|
class:form-autocomplete--has-value={selectedValues.length > 0}
|
|
568
626
|
>
|
|
569
627
|
<!-- Main input container styled like a textfield/textarea -->
|
|
628
|
+
<!-- svelte-ignore a11y_no_static_element_interactions — role="presentation"; keyboard interaction is handled by the <input> inside -->
|
|
570
629
|
<div
|
|
571
630
|
class="form-autocomplete__field"
|
|
572
631
|
class:form-autocomplete__field--focused={isOpen}
|
|
573
632
|
onclick={() => inputElement?.focus()}
|
|
574
|
-
onkeydown={() => {}}
|
|
575
633
|
role="presentation"
|
|
576
634
|
>
|
|
577
635
|
<!-- Selected tags -->
|
|
@@ -664,11 +722,7 @@
|
|
|
664
722
|
style={popoverStyle}
|
|
665
723
|
onmousedown={(e) => e.preventDefault()}
|
|
666
724
|
>
|
|
667
|
-
<ul
|
|
668
|
-
class="form-autocomplete__listbox"
|
|
669
|
-
role="listbox"
|
|
670
|
-
aria-label={t.suggestions}
|
|
671
|
-
>
|
|
725
|
+
<ul class="form-autocomplete__listbox" role="listbox" aria-label={t.suggestions}>
|
|
672
726
|
{#if isLoading}
|
|
673
727
|
<li class="form-autocomplete__status form-autocomplete__status--loading">
|
|
674
728
|
<Icon icon="heroicons:arrow-path" class="form-autocomplete__status-icon" />
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import { getSchemaOptions } from './types.js';
|
|
46
46
|
import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
|
|
47
47
|
import { getResolvedTheme } from '../../stores/settingsStore.svelte.js';
|
|
48
|
+
import { fieldComponentRegistry } from '../../form/fieldRegistry.js';
|
|
48
49
|
|
|
49
50
|
interface Props {
|
|
50
51
|
/** Unique key/id for the field */
|
|
@@ -224,6 +225,10 @@
|
|
|
224
225
|
return [];
|
|
225
226
|
});
|
|
226
227
|
|
|
228
|
+
const registeredAutocompleteComponent = $derived(
|
|
229
|
+
fieldType === 'autocomplete' ? fieldComponentRegistry.resolveFieldComponent(schema) : null
|
|
230
|
+
);
|
|
231
|
+
|
|
227
232
|
/**
|
|
228
233
|
* Get autocomplete value - can be string or string[] based on multiple setting
|
|
229
234
|
*/
|
|
@@ -387,6 +392,22 @@
|
|
|
387
392
|
{authProvider}
|
|
388
393
|
onChange={(val) => onChange(val)}
|
|
389
394
|
/>
|
|
395
|
+
{:else if fieldType === 'autocomplete' && schema.autocomplete && registeredAutocompleteComponent}
|
|
396
|
+
<registeredAutocompleteComponent.component
|
|
397
|
+
id={fieldKey}
|
|
398
|
+
value={autocompleteValue}
|
|
399
|
+
placeholder={schema.placeholder ?? ''}
|
|
400
|
+
{required}
|
|
401
|
+
{schema}
|
|
402
|
+
ariaDescribedBy={descriptionId}
|
|
403
|
+
disabled={isReadOnly}
|
|
404
|
+
{node}
|
|
405
|
+
{nodes}
|
|
406
|
+
{edges}
|
|
407
|
+
{workflowId}
|
|
408
|
+
{authProvider}
|
|
409
|
+
onChange={(val: unknown) => onChange(val)}
|
|
410
|
+
/>
|
|
390
411
|
{:else if fieldType === 'autocomplete' && schema.autocomplete}
|
|
391
412
|
<FormAutocomplete
|
|
392
413
|
id={fieldKey}
|
|
@@ -243,6 +243,7 @@
|
|
|
243
243
|
spellChecker={schema.spellChecker as boolean | undefined}
|
|
244
244
|
variables={schema.variables}
|
|
245
245
|
placeholderExample={schema.placeholderExample as string | undefined}
|
|
246
|
+
autocomplete={schema.autocomplete}
|
|
246
247
|
onChange={(val: unknown) => onChange(val)}
|
|
247
248
|
/>
|
|
248
249
|
{:else if fieldType === 'checkbox-group'}
|
|
@@ -135,7 +135,11 @@
|
|
|
135
135
|
{/if}
|
|
136
136
|
|
|
137
137
|
<!-- Options -->
|
|
138
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
class="choice-prompt__options"
|
|
140
|
+
role={isMultiple ? 'group' : 'radiogroup'}
|
|
141
|
+
aria-label={config.message}
|
|
142
|
+
>
|
|
139
143
|
{#each config.options as option (option.value)}
|
|
140
144
|
{@const isChecked = isResolved ? isOptionResolved(option) : selectedValues.has(option.value)}
|
|
141
145
|
<label
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
import TextInputPrompt from './TextInputPrompt.svelte';
|
|
15
15
|
import FormPrompt from './FormPrompt.svelte';
|
|
16
16
|
import ReviewPrompt from './ReviewPrompt.svelte';
|
|
17
|
+
import MessageTagStrip from '../playground/MessageTagStrip.svelte';
|
|
18
|
+
import HierarchyTrail from '../playground/HierarchyTrail.svelte';
|
|
19
|
+
import type { MessageHierarchyItem, MessageTag } from '../../types/playground.js';
|
|
17
20
|
import type {
|
|
18
21
|
Interrupt,
|
|
19
22
|
InterruptType,
|
|
@@ -49,9 +52,25 @@
|
|
|
49
52
|
showTimestamp?: boolean;
|
|
50
53
|
/** Callback to refresh messages after interrupt resolution */
|
|
51
54
|
onResolved?: () => void;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Hierarchy items forwarded from the parent playground message. Rendered
|
|
57
|
+
* as a chevron-separated trail in the footer.
|
|
58
|
+
*/
|
|
59
|
+
hierarchy?: MessageHierarchyItem[];
|
|
60
|
+
/**
|
|
61
|
+
* Server-emitted tags forwarded from the parent playground message.
|
|
62
|
+
* Rendered as chips in the footer.
|
|
63
|
+
*/
|
|
64
|
+
tags?: MessageTag[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let {
|
|
68
|
+
interrupt: initialInterrupt,
|
|
69
|
+
showTimestamp = true,
|
|
70
|
+
onResolved,
|
|
71
|
+
hierarchy,
|
|
72
|
+
tags
|
|
73
|
+
}: Props = $props();
|
|
55
74
|
|
|
56
75
|
/**
|
|
57
76
|
* Get the current interrupt state from the store.
|
|
@@ -61,6 +80,11 @@
|
|
|
61
80
|
getInterruptsMap().get(initialInterrupt.id) ?? addMachineState(initialInterrupt)
|
|
62
81
|
);
|
|
63
82
|
|
|
83
|
+
const hierarchyItems = $derived(hierarchy ?? []);
|
|
84
|
+
const tagItems = $derived(tags ?? []);
|
|
85
|
+
const hasHierarchy = $derived(hierarchyItems.length > 0);
|
|
86
|
+
const hasTags = $derived(tagItems.length > 0);
|
|
87
|
+
|
|
64
88
|
/**
|
|
65
89
|
* Helper to ensure interrupt has machine state
|
|
66
90
|
*/
|
|
@@ -262,7 +286,7 @@
|
|
|
262
286
|
<!-- Header -->
|
|
263
287
|
<div class="interrupt-bubble__header">
|
|
264
288
|
<span class="interrupt-bubble__type">
|
|
265
|
-
<Icon icon={getTypeIcon(currentInterrupt.type)} />
|
|
289
|
+
<Icon icon={getTypeIcon(currentInterrupt.type)} aria-hidden="true" />
|
|
266
290
|
{#if isResolved}
|
|
267
291
|
{currentInterrupt.machineState.status === 'cancelled'
|
|
268
292
|
? t.cancelled
|
|
@@ -274,9 +298,15 @@
|
|
|
274
298
|
{/if}
|
|
275
299
|
</span>
|
|
276
300
|
{#if showTimestamp}
|
|
277
|
-
<
|
|
301
|
+
<time
|
|
302
|
+
class="interrupt-bubble__timestamp"
|
|
303
|
+
datetime={currentInterrupt.resolvedAt ?? currentInterrupt.createdAt}
|
|
304
|
+
aria-label="sent at {formatTimestamp(
|
|
305
|
+
currentInterrupt.resolvedAt ?? currentInterrupt.createdAt
|
|
306
|
+
)}"
|
|
307
|
+
>
|
|
278
308
|
{formatTimestamp(currentInterrupt.resolvedAt ?? currentInterrupt.createdAt)}
|
|
279
|
-
</
|
|
309
|
+
</time>
|
|
280
310
|
{/if}
|
|
281
311
|
</div>
|
|
282
312
|
|
|
@@ -349,17 +379,21 @@
|
|
|
349
379
|
</div>
|
|
350
380
|
|
|
351
381
|
<!-- Footer -->
|
|
352
|
-
{#if currentInterrupt.nodeId || (currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation')}
|
|
382
|
+
{#if currentInterrupt.nodeId || hasHierarchy || hasTags || (currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation')}
|
|
353
383
|
<div class="interrupt-bubble__footer">
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
384
|
+
<div class="interrupt-bubble__attribution">
|
|
385
|
+
{#if currentInterrupt.nodeId}
|
|
386
|
+
<span
|
|
387
|
+
class="interrupt-bubble__node"
|
|
388
|
+
title={t.nodeIdTooltip({ id: currentInterrupt.nodeId })}
|
|
389
|
+
>
|
|
390
|
+
<Icon icon="mdi:graph" aria-hidden="true" />
|
|
391
|
+
<span>{t.fromWorkflow}</span>
|
|
392
|
+
</span>
|
|
393
|
+
{/if}
|
|
394
|
+
<HierarchyTrail items={hierarchyItems} />
|
|
395
|
+
<MessageTagStrip tags={tagItems} />
|
|
396
|
+
</div>
|
|
363
397
|
{#if currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation'}
|
|
364
398
|
<button
|
|
365
399
|
type="button"
|
|
@@ -367,7 +401,7 @@
|
|
|
367
401
|
onclick={handleCancel}
|
|
368
402
|
disabled={isSubmitting}
|
|
369
403
|
>
|
|
370
|
-
<Icon icon="mdi:close" />
|
|
404
|
+
<Icon icon="mdi:close" aria-hidden="true" />
|
|
371
405
|
<span>{t.cancel}</span>
|
|
372
406
|
</button>
|
|
373
407
|
{/if}
|
|
@@ -542,6 +576,7 @@
|
|
|
542
576
|
/* Footer */
|
|
543
577
|
.interrupt-bubble__footer {
|
|
544
578
|
display: flex;
|
|
579
|
+
flex-wrap: wrap;
|
|
545
580
|
align-items: center;
|
|
546
581
|
justify-content: space-between;
|
|
547
582
|
gap: var(--fd-space-xs);
|
|
@@ -565,6 +600,15 @@
|
|
|
565
600
|
border-top-color: var(--fd-interrupt-prompt-border-error);
|
|
566
601
|
}
|
|
567
602
|
|
|
603
|
+
.interrupt-bubble__attribution {
|
|
604
|
+
display: flex;
|
|
605
|
+
flex-wrap: wrap;
|
|
606
|
+
align-items: center;
|
|
607
|
+
gap: var(--fd-space-xs);
|
|
608
|
+
min-width: 0;
|
|
609
|
+
flex: 1 1 auto;
|
|
610
|
+
}
|
|
611
|
+
|
|
568
612
|
.interrupt-bubble__node {
|
|
569
613
|
display: flex;
|
|
570
614
|
align-items: center;
|
|
@@ -618,5 +662,19 @@
|
|
|
618
662
|
padding-left: var(--fd-space-lg);
|
|
619
663
|
padding-right: var(--fd-space-lg);
|
|
620
664
|
}
|
|
665
|
+
|
|
666
|
+
.interrupt-bubble__cancel-btn {
|
|
667
|
+
min-height: 2.5rem;
|
|
668
|
+
padding: var(--fd-space-xs) var(--fd-space-md);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
@media (prefers-reduced-motion: reduce) {
|
|
673
|
+
.interrupt-bubble {
|
|
674
|
+
animation: none;
|
|
675
|
+
}
|
|
676
|
+
.interrupt-bubble__cancel-btn {
|
|
677
|
+
transition: none;
|
|
678
|
+
}
|
|
621
679
|
}
|
|
622
680
|
</style>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MessageHierarchyItem, MessageTag } from '../../types/playground.js';
|
|
1
2
|
import type { Interrupt } from '../../types/interrupt.js';
|
|
2
3
|
import { type InterruptWithState } from '../../stores/interruptStore.svelte.js';
|
|
3
4
|
/**
|
|
@@ -10,6 +11,16 @@ interface Props {
|
|
|
10
11
|
showTimestamp?: boolean;
|
|
11
12
|
/** Callback to refresh messages after interrupt resolution */
|
|
12
13
|
onResolved?: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Hierarchy items forwarded from the parent playground message. Rendered
|
|
16
|
+
* as a chevron-separated trail in the footer.
|
|
17
|
+
*/
|
|
18
|
+
hierarchy?: MessageHierarchyItem[];
|
|
19
|
+
/**
|
|
20
|
+
* Server-emitted tags forwarded from the parent playground message.
|
|
21
|
+
* Rendered as chips in the footer.
|
|
22
|
+
*/
|
|
23
|
+
tags?: MessageTag[];
|
|
13
24
|
}
|
|
14
25
|
declare const InterruptBubble: import("svelte").Component<Props, {}, "">;
|
|
15
26
|
type InterruptBubble = ReturnType<typeof InterruptBubble>;
|