@flowdrop/flowdrop 1.11.0 → 1.13.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/dist/api/enhanced-client.d.ts +29 -16
- package/dist/api/enhanced-client.js +0 -14
- package/dist/components/ConfigForm.svelte +1 -0
- package/dist/components/PipelineStatus.svelte +9 -12
- package/dist/components/SchemaForm.svelte +1 -0
- package/dist/components/WorkflowEditor.svelte +3 -0
- package/dist/components/form/FormAutocomplete.svelte +67 -10
- package/dist/components/form/FormField.svelte +21 -0
- package/dist/components/form/FormFieldLight.svelte +1 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +24 -5
- package/dist/components/interrupt/ConfirmationPrompt.svelte +5 -0
- package/dist/components/interrupt/InterruptBubble.svelte +88 -17
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
- package/dist/components/interrupt/ReviewPrompt.svelte +20 -0
- package/dist/components/interrupt/TextInputPrompt.svelte +5 -0
- package/dist/components/nodes/GatewayNode.svelte +2 -6
- package/dist/components/nodes/WorkflowNode.svelte +2 -6
- package/dist/components/playground/ChatBubble.svelte +289 -0
- package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
- package/dist/components/playground/ChatInput.svelte +359 -0
- package/dist/components/playground/ChatInput.svelte.d.ts +14 -0
- package/dist/components/playground/ChatPanel.svelte +100 -724
- package/dist/components/playground/ChatPanel.svelte.d.ts +9 -26
- package/dist/components/playground/ControlPanel.svelte +496 -0
- package/dist/components/playground/ControlPanel.svelte.d.ts +20 -0
- package/dist/components/playground/ExecutionConsole.svelte +163 -0
- package/dist/components/playground/ExecutionConsole.svelte.d.ts +14 -0
- package/dist/components/playground/HierarchyTrail.svelte +88 -0
- package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
- package/dist/components/playground/LogRow.svelte +178 -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 +25 -737
- package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
- package/dist/components/playground/MessageCard.svelte +106 -0
- package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
- package/dist/components/playground/MessageMarkdown.svelte +160 -0
- package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
- package/dist/components/playground/MessageNotice.svelte +120 -0
- package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
- package/dist/components/playground/MessageStream.svelte +367 -0
- package/dist/components/playground/MessageStream.svelte.d.ts +27 -0
- package/dist/components/playground/MessageTagChip.svelte +99 -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 +284 -0
- package/dist/components/playground/PipelineKanbanView.svelte.d.ts +11 -0
- package/dist/components/playground/PipelinePanel.svelte +204 -65
- package/dist/components/playground/PipelinePanel.svelte.d.ts +3 -1
- package/dist/components/playground/PipelineTableView.svelte +376 -0
- package/dist/components/playground/PipelineTableView.svelte.d.ts +11 -0
- package/dist/components/playground/Playground.svelte +262 -1200
- package/dist/components/playground/Playground.svelte.d.ts +0 -13
- package/dist/components/playground/PlaygroundStudio.svelte +113 -61
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -1
- package/dist/components/playground/messageDisplay.d.ts +19 -0
- package/dist/components/playground/messageDisplay.js +62 -0
- package/dist/components/playground/pipelineViewUtils.svelte.d.ts +22 -0
- package/dist/components/playground/pipelineViewUtils.svelte.js +77 -0
- 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 +29 -0
- package/dist/messages/defaults.js +30 -0
- package/dist/playground/index.d.ts +6 -1
- package/dist/playground/index.js +6 -0
- package/dist/playground/mount.d.ts +3 -0
- package/dist/playground/mount.js +3 -2
- package/dist/schemas/v1/workflow.schema.json +10 -1
- package/dist/services/categoriesApi.d.ts +2 -1
- package/dist/services/categoriesApi.js +5 -3
- package/dist/services/portConfigApi.d.ts +2 -1
- package/dist/services/portConfigApi.js +5 -3
- package/dist/stores/playgroundStore.svelte.d.ts +6 -0
- package/dist/stores/playgroundStore.svelte.js +21 -1
- package/dist/svelte-app.d.ts +1 -0
- package/dist/svelte-app.js +5 -5
- package/dist/types/index.d.ts +41 -2
- package/dist/types/playground.d.ts +81 -2
- package/dist/types/playground.js +19 -7
- package/dist/utils/nodeStatus.js +15 -5
- package/package.json +6 -1
|
@@ -34,6 +34,34 @@ export declare class ApiError extends Error {
|
|
|
34
34
|
* const client = new EnhancedFlowDropApiClient(config);
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
+
export interface PipelineDataResponse {
|
|
38
|
+
status: string;
|
|
39
|
+
jobs: Array<Record<string, unknown>>;
|
|
40
|
+
node_statuses: Record<string, {
|
|
41
|
+
status: string;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}>;
|
|
44
|
+
job_status_summary: {
|
|
45
|
+
total: number;
|
|
46
|
+
pending: number;
|
|
47
|
+
running: number;
|
|
48
|
+
completed: number;
|
|
49
|
+
failed: number;
|
|
50
|
+
cancelled: number;
|
|
51
|
+
skipped?: number;
|
|
52
|
+
paused?: number;
|
|
53
|
+
interrupted?: number;
|
|
54
|
+
};
|
|
55
|
+
kanban_config?: {
|
|
56
|
+
columns: Array<{
|
|
57
|
+
key: string;
|
|
58
|
+
label: string;
|
|
59
|
+
statuses: string[];
|
|
60
|
+
icon?: string;
|
|
61
|
+
color?: string;
|
|
62
|
+
}>;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
37
65
|
export declare class EnhancedFlowDropApiClient {
|
|
38
66
|
private config;
|
|
39
67
|
private authProvider;
|
|
@@ -164,20 +192,5 @@ export declare class EnhancedFlowDropApiClient {
|
|
|
164
192
|
/**
|
|
165
193
|
* Fetch pipeline data including job information and status
|
|
166
194
|
*/
|
|
167
|
-
getPipelineData(pipelineId: string): Promise<
|
|
168
|
-
status: string;
|
|
169
|
-
jobs: Array<Record<string, unknown>>;
|
|
170
|
-
node_statuses: Record<string, {
|
|
171
|
-
status: string;
|
|
172
|
-
[key: string]: unknown;
|
|
173
|
-
}>;
|
|
174
|
-
job_status_summary: {
|
|
175
|
-
total: number;
|
|
176
|
-
pending: number;
|
|
177
|
-
running: number;
|
|
178
|
-
completed: number;
|
|
179
|
-
failed: number;
|
|
180
|
-
cancelled: number;
|
|
181
|
-
};
|
|
182
|
-
}>;
|
|
195
|
+
getPipelineData(pipelineId: string): Promise<PipelineDataResponse>;
|
|
183
196
|
}
|
|
@@ -28,20 +28,6 @@ export class ApiError extends Error {
|
|
|
28
28
|
this.errorData = errorData;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Enhanced HTTP API client for FlowDrop with configurable endpoints
|
|
33
|
-
*
|
|
34
|
-
* Supports pluggable authentication via AuthProvider interface.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```typescript
|
|
38
|
-
* // With AuthProvider
|
|
39
|
-
* const client = new EnhancedFlowDropApiClient(config, authProvider);
|
|
40
|
-
*
|
|
41
|
-
* // Without authentication
|
|
42
|
-
* const client = new EnhancedFlowDropApiClient(config);
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
31
|
export class EnhancedFlowDropApiClient {
|
|
46
32
|
config;
|
|
47
33
|
authProvider;
|
|
@@ -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
|
|
@@ -117,23 +117,20 @@
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
// Update node statuses based on job data
|
|
120
|
+
// Update node statuses based on job data — only set what the server reported
|
|
121
121
|
if (jobStatusData.node_statuses) {
|
|
122
122
|
const newNodeStatuses: Record<string, 'pending' | 'running' | 'completed' | 'error'> = {};
|
|
123
123
|
|
|
124
|
-
// Initialize all nodes as pending
|
|
125
|
-
if (workflow && workflow.nodes) {
|
|
126
|
-
workflow.nodes.forEach((node) => {
|
|
127
|
-
newNodeStatuses[node.id] = 'pending';
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Override with actual job statuses
|
|
132
124
|
for (const nodeId in jobStatusData.node_statuses) {
|
|
133
125
|
const status = jobStatusData.node_statuses[nodeId].status;
|
|
134
|
-
if (
|
|
135
|
-
newNodeStatuses[nodeId] =
|
|
136
|
-
|
|
126
|
+
if (status === 'failed' || status === 'cancelled') {
|
|
127
|
+
newNodeStatuses[nodeId] = 'error';
|
|
128
|
+
} else if (status === 'running' || status === 'paused' || status === 'interrupted') {
|
|
129
|
+
newNodeStatuses[nodeId] = 'running';
|
|
130
|
+
} else if (status === 'completed' || status === 'skipped') {
|
|
131
|
+
newNodeStatuses[nodeId] = 'completed';
|
|
132
|
+
} else if (status === 'pending' || status === 'idle') {
|
|
133
|
+
newNodeStatuses[nodeId] = 'pending';
|
|
137
134
|
}
|
|
138
135
|
}
|
|
139
136
|
nodeStatuses = newNodeStatuses;
|
|
@@ -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
|
|
@@ -855,6 +855,9 @@
|
|
|
855
855
|
{initialViewport}
|
|
856
856
|
colorMode={getResolvedTheme() as ColorMode}
|
|
857
857
|
fitView={getEditorSettings().fitViewOnLoad}
|
|
858
|
+
nodesDraggable={!props.lockWorkflow && !props.readOnly}
|
|
859
|
+
nodesConnectable={!props.lockWorkflow && !props.readOnly}
|
|
860
|
+
elementsSelectable={!props.lockWorkflow && !props.readOnly}
|
|
858
861
|
>
|
|
859
862
|
<Controls />
|
|
860
863
|
{#if !props.readOnly && !props.lockWorkflow && props.onToggleConsole}
|
|
@@ -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,33 @@
|
|
|
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
|
+
let prevDepFingerprint = depFingerprint;
|
|
125
|
+
|
|
126
|
+
$effect(() => {
|
|
127
|
+
if (Object.keys(params).length > 0 && !getFormValues) {
|
|
128
|
+
logger.warn(
|
|
129
|
+
'[FormAutocomplete] `params` is configured but no flowdrop:getFormValues context ' +
|
|
130
|
+
'was found. Dependent field values will not be passed to the autocomplete URL. ' +
|
|
131
|
+
'Ensure this component is rendered inside ConfigForm or SchemaForm.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
105
136
|
// Generate unique IDs for accessibility
|
|
106
137
|
// svelte-ignore state_referenced_locally — id prop never changes
|
|
107
138
|
const listboxId = `${id}-listbox`;
|
|
@@ -160,7 +191,13 @@
|
|
|
160
191
|
: `${baseUrl}${autocomplete.url}`;
|
|
161
192
|
|
|
162
193
|
const separator = url.includes('?') ? '&' : '?';
|
|
163
|
-
|
|
194
|
+
let fullUrl = `${url}${separator}${encodeURIComponent(queryParam)}=${encodeURIComponent(query)}`;
|
|
195
|
+
|
|
196
|
+
for (const [paramName, val] of Object.entries(depParamValues)) {
|
|
197
|
+
fullUrl += `&${encodeURIComponent(paramName)}=${encodeURIComponent(val)}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return fullUrl;
|
|
164
201
|
}
|
|
165
202
|
|
|
166
203
|
/**
|
|
@@ -198,25 +235,25 @@
|
|
|
198
235
|
|
|
199
236
|
isLoading = true;
|
|
200
237
|
error = null;
|
|
201
|
-
|
|
238
|
+
// Capture in a local const so the timeout closure and fetch signal
|
|
239
|
+
// always reference the same controller, even if a new request starts
|
|
240
|
+
// before the 5-second timeout fires.
|
|
241
|
+
const controller = new AbortController();
|
|
242
|
+
abortController = controller;
|
|
202
243
|
|
|
244
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
203
245
|
try {
|
|
204
246
|
// Build headers with authentication (call getter to get current value)
|
|
205
247
|
const headers = await buildFetchHeaders(getAuthProvider?.());
|
|
206
248
|
|
|
207
|
-
|
|
208
|
-
const timeoutId = setTimeout(() => {
|
|
209
|
-
abortController?.abort();
|
|
210
|
-
}, 5000);
|
|
249
|
+
timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
211
250
|
|
|
212
251
|
const response = await fetch(buildUrl(query), {
|
|
213
252
|
method: 'GET',
|
|
214
253
|
headers,
|
|
215
|
-
signal:
|
|
254
|
+
signal: controller.signal
|
|
216
255
|
});
|
|
217
256
|
|
|
218
|
-
clearTimeout(timeoutId);
|
|
219
|
-
|
|
220
257
|
if (!response.ok) {
|
|
221
258
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
222
259
|
}
|
|
@@ -240,6 +277,7 @@
|
|
|
240
277
|
error = err instanceof Error ? err.message : 'Failed to fetch suggestions';
|
|
241
278
|
suggestions = [];
|
|
242
279
|
} finally {
|
|
280
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
243
281
|
isLoading = false;
|
|
244
282
|
abortController = null;
|
|
245
283
|
}
|
|
@@ -297,6 +335,13 @@
|
|
|
297
335
|
setTimeout(() => {
|
|
298
336
|
hideDropdown();
|
|
299
337
|
|
|
338
|
+
// When dependent params are configured, the suggestion list is bound to the
|
|
339
|
+
// current values of sibling fields. Clear it on blur so that fetchOnFocus
|
|
340
|
+
// always re-fetches with the current dep values rather than showing stale results.
|
|
341
|
+
if (Object.keys(params).length > 0) {
|
|
342
|
+
suggestions = [];
|
|
343
|
+
}
|
|
344
|
+
|
|
300
345
|
// If not allowFreeText and single mode, validate the input
|
|
301
346
|
if (!allowFreeText && !multiple && inputValue !== '') {
|
|
302
347
|
const currentVal = selectedValues;
|
|
@@ -542,6 +587,18 @@
|
|
|
542
587
|
}
|
|
543
588
|
});
|
|
544
589
|
|
|
590
|
+
$effect(() => {
|
|
591
|
+
const current = depFingerprint;
|
|
592
|
+
if (current === prevDepFingerprint) return;
|
|
593
|
+
prevDepFingerprint = current;
|
|
594
|
+
// A dependency field changed — abort any in-flight fetch, clear state
|
|
595
|
+
abortController?.abort();
|
|
596
|
+
suggestions = [];
|
|
597
|
+
labelCache = new Map();
|
|
598
|
+
if (multiple) onChange([]);
|
|
599
|
+
else onChange('');
|
|
600
|
+
});
|
|
601
|
+
|
|
545
602
|
/**
|
|
546
603
|
* Cleanup on unmount
|
|
547
604
|
*/
|
|
@@ -567,11 +624,11 @@
|
|
|
567
624
|
class:form-autocomplete--has-value={selectedValues.length > 0}
|
|
568
625
|
>
|
|
569
626
|
<!-- Main input container styled like a textfield/textarea -->
|
|
627
|
+
<!-- svelte-ignore a11y_no_static_element_interactions — role="presentation"; keyboard interaction is handled by the <input> inside -->
|
|
570
628
|
<div
|
|
571
629
|
class="form-autocomplete__field"
|
|
572
630
|
class:form-autocomplete__field--focused={isOpen}
|
|
573
631
|
onclick={() => inputElement?.focus()}
|
|
574
|
-
onkeydown={() => {}}
|
|
575
632
|
role="presentation"
|
|
576
633
|
>
|
|
577
634
|
<!-- Selected tags -->
|
|
@@ -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'}
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
// Hoist the choice branch — counter, min, max, submit reads.
|
|
46
46
|
const t = $derived(m().interrupt.choice);
|
|
47
47
|
|
|
48
|
+
// Unique name for the radio/checkbox group so multiple ChoicePrompts on
|
|
49
|
+
// screen don't share the same HTML group and interfere with each other.
|
|
50
|
+
const groupName = `choice-option-${Math.random().toString(36).slice(2, 8)}`;
|
|
51
|
+
|
|
48
52
|
/** Local state for selected values */
|
|
49
53
|
let selectedValues = $state<Set<string>>(new Set());
|
|
50
54
|
|
|
@@ -131,7 +135,7 @@
|
|
|
131
135
|
{/if}
|
|
132
136
|
|
|
133
137
|
<!-- Options -->
|
|
134
|
-
<div class="choice-prompt__options" role={isMultiple ? 'group' : 'radiogroup'}>
|
|
138
|
+
<div class="choice-prompt__options" role={isMultiple ? 'group' : 'radiogroup'} aria-label={config.message}>
|
|
135
139
|
{#each config.options as option (option.value)}
|
|
136
140
|
{@const isChecked = isResolved ? isOptionResolved(option) : selectedValues.has(option.value)}
|
|
137
141
|
<label
|
|
@@ -141,7 +145,7 @@
|
|
|
141
145
|
>
|
|
142
146
|
<input
|
|
143
147
|
type={isMultiple ? 'checkbox' : 'radio'}
|
|
144
|
-
name=
|
|
148
|
+
name={groupName}
|
|
145
149
|
value={option.value}
|
|
146
150
|
checked={isChecked}
|
|
147
151
|
disabled={isResolved || isSubmitting}
|
|
@@ -295,9 +299,19 @@
|
|
|
295
299
|
|
|
296
300
|
.choice-prompt__input {
|
|
297
301
|
position: absolute;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
width: 1px;
|
|
303
|
+
height: 1px;
|
|
304
|
+
padding: 0;
|
|
305
|
+
margin: -1px;
|
|
306
|
+
overflow: hidden;
|
|
307
|
+
clip: rect(0, 0, 0, 0);
|
|
308
|
+
white-space: nowrap;
|
|
309
|
+
border: 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.choice-prompt__option:focus-within {
|
|
313
|
+
outline: 2px solid var(--fd-ring);
|
|
314
|
+
outline-offset: 2px;
|
|
301
315
|
}
|
|
302
316
|
|
|
303
317
|
.choice-prompt__checkmark {
|
|
@@ -372,6 +386,11 @@
|
|
|
372
386
|
transform: translateY(-1px);
|
|
373
387
|
}
|
|
374
388
|
|
|
389
|
+
.choice-prompt__submit:focus-visible {
|
|
390
|
+
outline: 2px solid var(--fd-ring);
|
|
391
|
+
outline-offset: 2px;
|
|
392
|
+
}
|
|
393
|
+
|
|
375
394
|
.choice-prompt__submit:disabled {
|
|
376
395
|
opacity: 0.5;
|
|
377
396
|
cursor: not-allowed;
|
|
@@ -14,6 +14,12 @@
|
|
|
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 {
|
|
20
|
+
MessageHierarchyItem,
|
|
21
|
+
MessageTag
|
|
22
|
+
} from '../../types/playground.js';
|
|
17
23
|
import type {
|
|
18
24
|
Interrupt,
|
|
19
25
|
InterruptType,
|
|
@@ -49,9 +55,25 @@
|
|
|
49
55
|
showTimestamp?: boolean;
|
|
50
56
|
/** Callback to refresh messages after interrupt resolution */
|
|
51
57
|
onResolved?: () => void;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Hierarchy items forwarded from the parent playground message. Rendered
|
|
60
|
+
* as a chevron-separated trail in the footer.
|
|
61
|
+
*/
|
|
62
|
+
hierarchy?: MessageHierarchyItem[];
|
|
63
|
+
/**
|
|
64
|
+
* Server-emitted tags forwarded from the parent playground message.
|
|
65
|
+
* Rendered as chips in the footer.
|
|
66
|
+
*/
|
|
67
|
+
tags?: MessageTag[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let {
|
|
71
|
+
interrupt: initialInterrupt,
|
|
72
|
+
showTimestamp = true,
|
|
73
|
+
onResolved,
|
|
74
|
+
hierarchy,
|
|
75
|
+
tags
|
|
76
|
+
}: Props = $props();
|
|
55
77
|
|
|
56
78
|
/**
|
|
57
79
|
* Get the current interrupt state from the store.
|
|
@@ -61,6 +83,11 @@
|
|
|
61
83
|
getInterruptsMap().get(initialInterrupt.id) ?? addMachineState(initialInterrupt)
|
|
62
84
|
);
|
|
63
85
|
|
|
86
|
+
const hierarchyItems = $derived(hierarchy ?? []);
|
|
87
|
+
const tagItems = $derived(tags ?? []);
|
|
88
|
+
const hasHierarchy = $derived(hierarchyItems.length > 0);
|
|
89
|
+
const hasTags = $derived(tagItems.length > 0);
|
|
90
|
+
|
|
64
91
|
/**
|
|
65
92
|
* Helper to ensure interrupt has machine state
|
|
66
93
|
*/
|
|
@@ -256,11 +283,13 @@
|
|
|
256
283
|
class:interrupt-bubble--cancelled={currentInterrupt.machineState.status === 'cancelled'}
|
|
257
284
|
class:interrupt-bubble--submitting={isSubmitting}
|
|
258
285
|
class:interrupt-bubble--error={currentInterrupt.machineState.status === 'error'}
|
|
286
|
+
role="group"
|
|
287
|
+
aria-label={getTypeLabel(currentInterrupt.type)}
|
|
259
288
|
>
|
|
260
289
|
<!-- Header -->
|
|
261
290
|
<div class="interrupt-bubble__header">
|
|
262
291
|
<span class="interrupt-bubble__type">
|
|
263
|
-
<Icon icon={getTypeIcon(currentInterrupt.type)} />
|
|
292
|
+
<Icon icon={getTypeIcon(currentInterrupt.type)} aria-hidden="true" />
|
|
264
293
|
{#if isResolved}
|
|
265
294
|
{currentInterrupt.machineState.status === 'cancelled'
|
|
266
295
|
? t.cancelled
|
|
@@ -272,9 +301,13 @@
|
|
|
272
301
|
{/if}
|
|
273
302
|
</span>
|
|
274
303
|
{#if showTimestamp}
|
|
275
|
-
<
|
|
304
|
+
<time
|
|
305
|
+
class="interrupt-bubble__timestamp"
|
|
306
|
+
datetime={currentInterrupt.resolvedAt ?? currentInterrupt.createdAt}
|
|
307
|
+
aria-label="sent at {formatTimestamp(currentInterrupt.resolvedAt ?? currentInterrupt.createdAt)}"
|
|
308
|
+
>
|
|
276
309
|
{formatTimestamp(currentInterrupt.resolvedAt ?? currentInterrupt.createdAt)}
|
|
277
|
-
</
|
|
310
|
+
</time>
|
|
278
311
|
{/if}
|
|
279
312
|
</div>
|
|
280
313
|
|
|
@@ -347,17 +380,21 @@
|
|
|
347
380
|
</div>
|
|
348
381
|
|
|
349
382
|
<!-- Footer -->
|
|
350
|
-
{#if currentInterrupt.nodeId || (currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation')}
|
|
383
|
+
{#if currentInterrupt.nodeId || hasHierarchy || hasTags || (currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation')}
|
|
351
384
|
<div class="interrupt-bubble__footer">
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
385
|
+
<div class="interrupt-bubble__attribution">
|
|
386
|
+
{#if currentInterrupt.nodeId}
|
|
387
|
+
<span
|
|
388
|
+
class="interrupt-bubble__node"
|
|
389
|
+
title={t.nodeIdTooltip({ id: currentInterrupt.nodeId })}
|
|
390
|
+
>
|
|
391
|
+
<Icon icon="mdi:graph" aria-hidden="true" />
|
|
392
|
+
<span>{t.fromWorkflow}</span>
|
|
393
|
+
</span>
|
|
394
|
+
{/if}
|
|
395
|
+
<HierarchyTrail items={hierarchyItems} />
|
|
396
|
+
<MessageTagStrip tags={tagItems} />
|
|
397
|
+
</div>
|
|
361
398
|
{#if currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation'}
|
|
362
399
|
<button
|
|
363
400
|
type="button"
|
|
@@ -365,7 +402,7 @@
|
|
|
365
402
|
onclick={handleCancel}
|
|
366
403
|
disabled={isSubmitting}
|
|
367
404
|
>
|
|
368
|
-
<Icon icon="mdi:close" />
|
|
405
|
+
<Icon icon="mdi:close" aria-hidden="true" />
|
|
369
406
|
<span>{t.cancel}</span>
|
|
370
407
|
</button>
|
|
371
408
|
{/if}
|
|
@@ -517,6 +554,11 @@
|
|
|
517
554
|
background-color: var(--fd-error-hover);
|
|
518
555
|
}
|
|
519
556
|
|
|
557
|
+
.interrupt-bubble__retry-btn:focus-visible {
|
|
558
|
+
outline: 2px solid var(--fd-ring);
|
|
559
|
+
outline-offset: 2px;
|
|
560
|
+
}
|
|
561
|
+
|
|
520
562
|
/* Body - prompt content area, full width */
|
|
521
563
|
.interrupt-bubble__body {
|
|
522
564
|
padding: var(--fd-space-xl);
|
|
@@ -535,6 +577,7 @@
|
|
|
535
577
|
/* Footer */
|
|
536
578
|
.interrupt-bubble__footer {
|
|
537
579
|
display: flex;
|
|
580
|
+
flex-wrap: wrap;
|
|
538
581
|
align-items: center;
|
|
539
582
|
justify-content: space-between;
|
|
540
583
|
gap: var(--fd-space-xs);
|
|
@@ -558,6 +601,15 @@
|
|
|
558
601
|
border-top-color: var(--fd-interrupt-prompt-border-error);
|
|
559
602
|
}
|
|
560
603
|
|
|
604
|
+
.interrupt-bubble__attribution {
|
|
605
|
+
display: flex;
|
|
606
|
+
flex-wrap: wrap;
|
|
607
|
+
align-items: center;
|
|
608
|
+
gap: var(--fd-space-xs);
|
|
609
|
+
min-width: 0;
|
|
610
|
+
flex: 1 1 auto;
|
|
611
|
+
}
|
|
612
|
+
|
|
561
613
|
.interrupt-bubble__node {
|
|
562
614
|
display: flex;
|
|
563
615
|
align-items: center;
|
|
@@ -589,6 +641,11 @@
|
|
|
589
641
|
background-color: var(--fd-error-muted);
|
|
590
642
|
}
|
|
591
643
|
|
|
644
|
+
.interrupt-bubble__cancel-btn:focus-visible {
|
|
645
|
+
outline: 2px solid var(--fd-ring);
|
|
646
|
+
outline-offset: 2px;
|
|
647
|
+
}
|
|
648
|
+
|
|
592
649
|
.interrupt-bubble__cancel-btn:disabled {
|
|
593
650
|
opacity: 0.5;
|
|
594
651
|
cursor: not-allowed;
|
|
@@ -606,5 +663,19 @@
|
|
|
606
663
|
padding-left: var(--fd-space-lg);
|
|
607
664
|
padding-right: var(--fd-space-lg);
|
|
608
665
|
}
|
|
666
|
+
|
|
667
|
+
.interrupt-bubble__cancel-btn {
|
|
668
|
+
min-height: 2.5rem;
|
|
669
|
+
padding: var(--fd-space-xs) var(--fd-space-md);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
@media (prefers-reduced-motion: reduce) {
|
|
674
|
+
.interrupt-bubble {
|
|
675
|
+
animation: none;
|
|
676
|
+
}
|
|
677
|
+
.interrupt-bubble__cancel-btn {
|
|
678
|
+
transition: none;
|
|
679
|
+
}
|
|
609
680
|
}
|
|
610
681
|
</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>;
|
|
@@ -533,6 +533,11 @@
|
|
|
533
533
|
color: var(--fd-error);
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
.review-prompt__bulk-btn:focus-visible {
|
|
537
|
+
outline: 2px solid var(--fd-ring);
|
|
538
|
+
outline-offset: 2px;
|
|
539
|
+
}
|
|
540
|
+
|
|
536
541
|
.review-prompt__bulk-btn:disabled {
|
|
537
542
|
opacity: 0.5;
|
|
538
543
|
cursor: not-allowed;
|
|
@@ -630,6 +635,11 @@
|
|
|
630
635
|
color: var(--fd-error-foreground);
|
|
631
636
|
}
|
|
632
637
|
|
|
638
|
+
.review-prompt__toggle-btn:focus-visible {
|
|
639
|
+
outline: 2px solid var(--fd-ring);
|
|
640
|
+
outline-offset: 2px;
|
|
641
|
+
}
|
|
642
|
+
|
|
633
643
|
.review-prompt__toggle-btn:disabled {
|
|
634
644
|
opacity: 0.5;
|
|
635
645
|
cursor: not-allowed;
|
|
@@ -722,6 +732,11 @@
|
|
|
722
732
|
border-color: var(--fd-border-strong);
|
|
723
733
|
}
|
|
724
734
|
|
|
735
|
+
.review-prompt__html-toggle-btn:focus-visible {
|
|
736
|
+
outline: 2px solid var(--fd-ring);
|
|
737
|
+
outline-offset: 2px;
|
|
738
|
+
}
|
|
739
|
+
|
|
725
740
|
/* Raw HTML code display */
|
|
726
741
|
.review-prompt__raw-html {
|
|
727
742
|
font-family: var(--fd-review-font-mono);
|
|
@@ -820,6 +835,11 @@
|
|
|
820
835
|
transform: translateY(-1px);
|
|
821
836
|
}
|
|
822
837
|
|
|
838
|
+
.review-prompt__submit:focus-visible {
|
|
839
|
+
outline: 2px solid var(--fd-ring);
|
|
840
|
+
outline-offset: 2px;
|
|
841
|
+
}
|
|
842
|
+
|
|
823
843
|
.review-prompt__submit:disabled {
|
|
824
844
|
opacity: 0.5;
|
|
825
845
|
cursor: not-allowed;
|