@d34dman/flowdrop 0.0.37 → 0.0.39

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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/NodeSidebar.svelte +1 -0
  3. package/dist/components/form/FormCodeEditor.svelte +6 -1
  4. package/dist/components/interrupt/ChoicePrompt.svelte +389 -0
  5. package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +21 -0
  6. package/dist/components/interrupt/ConfirmationPrompt.svelte +280 -0
  7. package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +23 -0
  8. package/dist/components/interrupt/FormPrompt.svelte +223 -0
  9. package/dist/components/interrupt/FormPrompt.svelte.d.ts +21 -0
  10. package/dist/components/interrupt/InterruptBubble.svelte +621 -0
  11. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +16 -0
  12. package/dist/components/interrupt/TextInputPrompt.svelte +333 -0
  13. package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +21 -0
  14. package/dist/components/interrupt/index.d.ts +13 -0
  15. package/dist/components/interrupt/index.js +15 -0
  16. package/dist/components/nodes/GatewayNode.svelte +1 -3
  17. package/dist/components/nodes/IdeaNode.svelte +30 -35
  18. package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
  19. package/dist/components/nodes/SimpleNode.svelte +1 -3
  20. package/dist/components/nodes/TerminalNode.svelte +1 -3
  21. package/dist/components/nodes/ToolNode.svelte +2 -2
  22. package/dist/components/nodes/WorkflowNode.svelte +1 -3
  23. package/dist/components/playground/ChatPanel.svelte +144 -7
  24. package/dist/components/playground/ChatPanel.svelte.d.ts +2 -0
  25. package/dist/components/playground/MessageBubble.svelte +1 -3
  26. package/dist/components/playground/Playground.svelte +50 -5
  27. package/dist/components/playground/PlaygroundModal.svelte +8 -7
  28. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
  29. package/dist/config/endpoints.d.ts +12 -0
  30. package/dist/config/endpoints.js +7 -0
  31. package/dist/playground/index.d.ts +5 -0
  32. package/dist/playground/index.js +21 -0
  33. package/dist/playground/mount.d.ts +3 -3
  34. package/dist/playground/mount.js +30 -22
  35. package/dist/services/interruptService.d.ts +133 -0
  36. package/dist/services/interruptService.js +279 -0
  37. package/dist/stores/interruptStore.d.ts +200 -0
  38. package/dist/stores/interruptStore.js +424 -0
  39. package/dist/stores/playgroundStore.d.ts +11 -1
  40. package/dist/stores/playgroundStore.js +34 -0
  41. package/dist/styles/base.css +89 -0
  42. package/dist/types/index.d.ts +1 -1
  43. package/dist/types/interrupt.d.ts +305 -0
  44. package/dist/types/interrupt.js +126 -0
  45. package/dist/types/interruptState.d.ts +211 -0
  46. package/dist/types/interruptState.js +308 -0
  47. package/dist/utils/colors.js +1 -0
  48. package/dist/utils/connections.js +2 -2
  49. package/dist/utils/icons.js +1 -0
  50. package/package.json +1 -1
@@ -0,0 +1,621 @@
1
+ <!--
2
+ InterruptBubble Component
3
+
4
+ Container component for rendering interrupt prompts inline in the chat flow.
5
+ Displays the appropriate prompt component based on interrupt type.
6
+ Handles resolve/cancel actions using state machine for safe transitions.
7
+ Styled with BEM syntax similar to MessageBubble.
8
+ -->
9
+
10
+ <script lang="ts">
11
+ import Icon from '@iconify/svelte';
12
+ import ConfirmationPrompt from './ConfirmationPrompt.svelte';
13
+ import ChoicePrompt from './ChoicePrompt.svelte';
14
+ import TextInputPrompt from './TextInputPrompt.svelte';
15
+ import FormPrompt from './FormPrompt.svelte';
16
+ import type {
17
+ Interrupt,
18
+ InterruptType,
19
+ ConfirmationConfig,
20
+ ChoiceConfig,
21
+ TextConfig,
22
+ FormConfig
23
+ } from '../../types/interrupt.js';
24
+ import {
25
+ isTerminalState,
26
+ isSubmitting as checkIsSubmitting,
27
+ getErrorMessage,
28
+ getResolvedValue
29
+ } from '../../types/interruptState.js';
30
+ import {
31
+ interrupts,
32
+ interruptActions,
33
+ type InterruptWithState
34
+ } from '../../stores/interruptStore.js';
35
+ import { interruptService } from '../../services/interruptService.js';
36
+
37
+ /**
38
+ * Component props
39
+ */
40
+ interface Props {
41
+ /** The interrupt to display (initial data, used for ID lookup) */
42
+ interrupt: Interrupt | InterruptWithState;
43
+ /** Whether to show the timestamp */
44
+ showTimestamp?: boolean;
45
+ /** Callback to refresh messages after interrupt resolution */
46
+ onResolved?: () => void;
47
+ }
48
+
49
+ let { interrupt: initialInterrupt, showTimestamp = true, onResolved }: Props = $props();
50
+
51
+ /**
52
+ * Get the current interrupt state from the store.
53
+ * This ensures we react to store updates (like status changes).
54
+ */
55
+ const currentInterrupt = $derived(
56
+ $interrupts.get(initialInterrupt.id) ?? addMachineState(initialInterrupt)
57
+ );
58
+
59
+ /**
60
+ * Helper to ensure interrupt has machine state
61
+ */
62
+ function addMachineState(interrupt: Interrupt | InterruptWithState): InterruptWithState {
63
+ if ('machineState' in interrupt) {
64
+ return interrupt;
65
+ }
66
+ return {
67
+ ...interrupt,
68
+ machineState: { status: 'idle' }
69
+ };
70
+ }
71
+
72
+ /** Whether this interrupt is in a terminal state (resolved or cancelled) */
73
+ const isResolved = $derived(isTerminalState(currentInterrupt.machineState));
74
+
75
+ /** Whether this interrupt is currently submitting */
76
+ const isSubmitting = $derived(checkIsSubmitting(currentInterrupt.machineState));
77
+
78
+ /** Error message for this interrupt */
79
+ const error = $derived(getErrorMessage(currentInterrupt.machineState));
80
+
81
+ /** Resolved value for display */
82
+ const resolvedValue = $derived(getResolvedValue(currentInterrupt.machineState));
83
+
84
+ /**
85
+ * Get the icon for the interrupt type
86
+ */
87
+ function getTypeIcon(type: InterruptType): string {
88
+ switch (type) {
89
+ case 'confirmation':
90
+ return 'mdi:help-circle';
91
+ case 'choice':
92
+ return 'mdi:format-list-bulleted';
93
+ case 'text':
94
+ return 'mdi:text-box';
95
+ case 'form':
96
+ return 'mdi:form-select';
97
+ default:
98
+ return 'mdi:bell';
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get the label for the interrupt type
104
+ */
105
+ function getTypeLabel(type: InterruptType): string {
106
+ switch (type) {
107
+ case 'confirmation':
108
+ return 'Confirmation Required';
109
+ case 'choice':
110
+ return 'Selection Required';
111
+ case 'text':
112
+ return 'Input Required';
113
+ case 'form':
114
+ return 'Form Required';
115
+ default:
116
+ return 'Action Required';
117
+ }
118
+ }
119
+
120
+ /** Get resolved label for the header when resolved */
121
+ function getResolvedLabel(type: InterruptType): string {
122
+ switch (type) {
123
+ case 'confirmation':
124
+ return 'Confirmation Submitted';
125
+ case 'choice':
126
+ return 'Selection Made';
127
+ case 'text':
128
+ return 'Input Submitted';
129
+ case 'form':
130
+ return 'Form Submitted';
131
+ default:
132
+ return 'Response Submitted';
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Format timestamp for display
138
+ */
139
+ function formatTimestamp(timestamp: string): string {
140
+ const date = new Date(timestamp);
141
+ return date.toLocaleTimeString('en-US', {
142
+ hour12: false,
143
+ hour: '2-digit',
144
+ minute: '2-digit',
145
+ second: '2-digit'
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Handle resolve action using state machine
151
+ */
152
+ async function handleResolve(value: unknown): Promise<void> {
153
+ // Start the submission - state machine validates this transition
154
+ const startResult = interruptActions.startSubmit(currentInterrupt.id, value);
155
+ if (!startResult.valid) {
156
+ console.warn('[InterruptBubble] Cannot submit:', startResult.error);
157
+ return;
158
+ }
159
+
160
+ try {
161
+ // Call API if service is configured
162
+ if (interruptService.isConfigured()) {
163
+ await interruptService.resolveInterrupt(currentInterrupt.id, value);
164
+ }
165
+
166
+ // Mark as successful - transitions to resolved state
167
+ interruptActions.submitSuccess(currentInterrupt.id);
168
+
169
+ // Notify parent to refresh messages
170
+ onResolved?.();
171
+ } catch (err) {
172
+ // Mark as failed - transitions to error state (can retry)
173
+ const errorMessage = err instanceof Error ? err.message : 'Failed to submit response';
174
+ interruptActions.submitFailure(currentInterrupt.id, errorMessage);
175
+ console.error('[InterruptBubble] Resolve error:', err);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Handle cancel action using state machine
181
+ */
182
+ async function handleCancel(): Promise<void> {
183
+ // Start the cancel - state machine validates this transition
184
+ const startResult = interruptActions.startCancel(currentInterrupt.id);
185
+ if (!startResult.valid) {
186
+ console.warn('[InterruptBubble] Cannot cancel:', startResult.error);
187
+ return;
188
+ }
189
+
190
+ try {
191
+ // Call API if service is configured
192
+ if (interruptService.isConfigured()) {
193
+ await interruptService.cancelInterrupt(currentInterrupt.id);
194
+ }
195
+
196
+ // Mark as successful - transitions to cancelled state
197
+ interruptActions.submitSuccess(currentInterrupt.id);
198
+
199
+ // Notify parent to refresh messages
200
+ onResolved?.();
201
+ } catch (err) {
202
+ // Mark as failed - transitions to error state (can retry)
203
+ const errorMessage = err instanceof Error ? err.message : 'Failed to cancel';
204
+ interruptActions.submitFailure(currentInterrupt.id, errorMessage);
205
+ console.error('[InterruptBubble] Cancel error:', err);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Handle retry after error
211
+ */
212
+ function handleRetry(): void {
213
+ interruptActions.retry(currentInterrupt.id);
214
+ }
215
+
216
+ // Typed config getters for each prompt type
217
+ const confirmationConfig = $derived(currentInterrupt.config as ConfirmationConfig);
218
+ const choiceConfig = $derived(currentInterrupt.config as ChoiceConfig);
219
+ const textConfig = $derived(currentInterrupt.config as TextConfig);
220
+ const formConfig = $derived(currentInterrupt.config as FormConfig);
221
+
222
+ // Determine the actual resolved value to pass to prompt components
223
+ const displayResolvedValue = $derived(resolvedValue ?? currentInterrupt.responseValue);
224
+ </script>
225
+
226
+ <div
227
+ class="interrupt-bubble"
228
+ class:interrupt-bubble--completed={currentInterrupt.machineState.status === 'resolved'}
229
+ class:interrupt-bubble--cancelled={currentInterrupt.machineState.status === 'cancelled'}
230
+ class:interrupt-bubble--submitting={isSubmitting}
231
+ class:interrupt-bubble--error={currentInterrupt.machineState.status === 'error'}
232
+ >
233
+ <!-- Avatar / Icon -->
234
+ <div class="interrupt-bubble__avatar">
235
+ {#if currentInterrupt.machineState.status === 'cancelled'}
236
+ <Icon icon="mdi:close-circle" />
237
+ {:else if currentInterrupt.machineState.status === 'resolved'}
238
+ <Icon icon="mdi:check-circle" />
239
+ {:else if currentInterrupt.machineState.status === 'error'}
240
+ <Icon icon="mdi:alert-circle" />
241
+ {:else}
242
+ <Icon icon="mdi:bell-ring" />
243
+ {/if}
244
+ </div>
245
+
246
+ <!-- Content -->
247
+ <div class="interrupt-bubble__content">
248
+ <!-- Header -->
249
+ <div class="interrupt-bubble__header">
250
+ <span class="interrupt-bubble__type">
251
+ <Icon icon={getTypeIcon(currentInterrupt.type)} />
252
+ {#if isResolved}
253
+ {currentInterrupt.machineState.status === 'cancelled'
254
+ ? 'Cancelled'
255
+ : getResolvedLabel(currentInterrupt.type)}
256
+ {:else if currentInterrupt.machineState.status === 'error'}
257
+ Error - Click to Retry
258
+ {:else}
259
+ {getTypeLabel(currentInterrupt.type)}
260
+ {/if}
261
+ </span>
262
+ {#if showTimestamp}
263
+ <span class="interrupt-bubble__timestamp">
264
+ {formatTimestamp(currentInterrupt.resolvedAt ?? currentInterrupt.createdAt)}
265
+ </span>
266
+ {/if}
267
+ </div>
268
+
269
+ <!-- Error message with retry button -->
270
+ {#if currentInterrupt.machineState.status === 'error'}
271
+ <div class="interrupt-bubble__error">
272
+ <Icon icon="mdi:alert-circle" />
273
+ <span>{error}</span>
274
+ <button type="button" class="interrupt-bubble__retry-btn" onclick={handleRetry}>
275
+ <Icon icon="mdi:refresh" />
276
+ Retry
277
+ </button>
278
+ </div>
279
+ {/if}
280
+
281
+ <!-- Prompt content based on type -->
282
+ <div class="interrupt-bubble__prompt">
283
+ {#if currentInterrupt.type === 'confirmation'}
284
+ <ConfirmationPrompt
285
+ config={confirmationConfig}
286
+ {isResolved}
287
+ resolvedValue={displayResolvedValue as boolean | undefined}
288
+ {isSubmitting}
289
+ {error}
290
+ onConfirm={() => handleResolve(true)}
291
+ onDecline={() => handleResolve(false)}
292
+ />
293
+ {:else if currentInterrupt.type === 'choice'}
294
+ <ChoicePrompt
295
+ config={choiceConfig}
296
+ {isResolved}
297
+ resolvedValue={displayResolvedValue as string | string[] | undefined}
298
+ {isSubmitting}
299
+ {error}
300
+ onSubmit={(value) => handleResolve(value)}
301
+ />
302
+ {:else if currentInterrupt.type === 'text'}
303
+ <TextInputPrompt
304
+ config={textConfig}
305
+ {isResolved}
306
+ resolvedValue={displayResolvedValue as string | undefined}
307
+ {isSubmitting}
308
+ {error}
309
+ onSubmit={(value) => handleResolve(value)}
310
+ />
311
+ {:else if currentInterrupt.type === 'form'}
312
+ <FormPrompt
313
+ config={formConfig}
314
+ {isResolved}
315
+ resolvedValue={displayResolvedValue as Record<string, unknown> | undefined}
316
+ {isSubmitting}
317
+ {error}
318
+ onSubmit={(value) => handleResolve(value)}
319
+ />
320
+ {/if}
321
+ </div>
322
+
323
+ <!-- Cancel button (if allowed and not in terminal state) -->
324
+ {#if currentInterrupt.allowCancel && !isResolved && currentInterrupt.type !== 'confirmation'}
325
+ <div class="interrupt-bubble__cancel-wrapper">
326
+ <button
327
+ type="button"
328
+ class="interrupt-bubble__cancel-btn"
329
+ onclick={handleCancel}
330
+ disabled={isSubmitting}
331
+ >
332
+ <Icon icon="mdi:close" />
333
+ <span>Cancel</span>
334
+ </button>
335
+ </div>
336
+ {/if}
337
+
338
+ <!-- Node info footer -->
339
+ {#if currentInterrupt.nodeId}
340
+ <div class="interrupt-bubble__footer">
341
+ <span class="interrupt-bubble__node" title="Node ID: {currentInterrupt.nodeId}">
342
+ <Icon icon="mdi:graph" />
343
+ <span>From workflow node</span>
344
+ </span>
345
+ </div>
346
+ {/if}
347
+ </div>
348
+ </div>
349
+
350
+ <style>
351
+ /* Uses design tokens from base.css: --flowdrop-interrupt-* */
352
+ .interrupt-bubble {
353
+ display: flex;
354
+ gap: 0.75rem;
355
+ padding: 1rem 1.25rem;
356
+ margin: 0.75rem 1rem;
357
+ border-radius: 0.75rem;
358
+ background: var(--flowdrop-interrupt-pending-bg);
359
+ border: 1px solid var(--flowdrop-interrupt-pending-border);
360
+ box-shadow: 0 2px 8px var(--flowdrop-interrupt-pending-shadow);
361
+ animation: interruptSlideIn 0.3s ease-out;
362
+ }
363
+
364
+ @keyframes interruptSlideIn {
365
+ from {
366
+ opacity: 0;
367
+ transform: translateY(12px);
368
+ }
369
+ to {
370
+ opacity: 1;
371
+ transform: translateY(0);
372
+ }
373
+ }
374
+
375
+ /* Completed state - neutral blue to indicate response received without implying good/bad */
376
+ .interrupt-bubble--completed {
377
+ background: var(--flowdrop-interrupt-completed-bg);
378
+ border-color: var(--flowdrop-interrupt-completed-border);
379
+ box-shadow: 0 2px 8px var(--flowdrop-interrupt-completed-shadow);
380
+ }
381
+
382
+ .interrupt-bubble--cancelled {
383
+ background: var(--flowdrop-interrupt-cancelled-bg);
384
+ border-color: var(--flowdrop-interrupt-cancelled-border);
385
+ box-shadow: 0 2px 8px var(--flowdrop-interrupt-cancelled-shadow);
386
+ }
387
+
388
+ .interrupt-bubble--error {
389
+ background: var(--flowdrop-interrupt-error-bg);
390
+ border-color: var(--flowdrop-interrupt-error-border);
391
+ box-shadow: 0 2px 8px var(--flowdrop-interrupt-error-shadow);
392
+ }
393
+
394
+ .interrupt-bubble--submitting {
395
+ opacity: 0.9;
396
+ }
397
+
398
+ /* Avatar */
399
+ .interrupt-bubble__avatar {
400
+ flex-shrink: 0;
401
+ width: 2.25rem;
402
+ height: 2.25rem;
403
+ display: flex;
404
+ align-items: center;
405
+ justify-content: center;
406
+ border-radius: 50%;
407
+ background-color: var(--flowdrop-interrupt-pending-avatar);
408
+ color: #ffffff;
409
+ font-size: 1.125rem;
410
+ }
411
+
412
+ .interrupt-bubble--completed .interrupt-bubble__avatar {
413
+ background-color: var(--flowdrop-interrupt-completed-avatar);
414
+ }
415
+
416
+ .interrupt-bubble--cancelled .interrupt-bubble__avatar {
417
+ background-color: var(--flowdrop-interrupt-cancelled-avatar);
418
+ }
419
+
420
+ .interrupt-bubble--error .interrupt-bubble__avatar {
421
+ background-color: var(--flowdrop-interrupt-error-avatar);
422
+ }
423
+
424
+ /* Content */
425
+ .interrupt-bubble__content {
426
+ flex: 1;
427
+ min-width: 0;
428
+ display: flex;
429
+ flex-direction: column;
430
+ gap: 0.75rem;
431
+ }
432
+
433
+ /* Header */
434
+ .interrupt-bubble__header {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ gap: 0.5rem;
439
+ }
440
+
441
+ .interrupt-bubble__type {
442
+ display: flex;
443
+ align-items: center;
444
+ gap: 0.375rem;
445
+ font-weight: 600;
446
+ font-size: 0.875rem;
447
+ color: var(--flowdrop-interrupt-pending-text);
448
+ }
449
+
450
+ .interrupt-bubble--completed .interrupt-bubble__type {
451
+ color: var(--flowdrop-interrupt-completed-text);
452
+ }
453
+
454
+ .interrupt-bubble--cancelled .interrupt-bubble__type {
455
+ color: var(--flowdrop-interrupt-cancelled-text);
456
+ }
457
+
458
+ .interrupt-bubble--error .interrupt-bubble__type {
459
+ color: var(--flowdrop-interrupt-error-text);
460
+ }
461
+
462
+ .interrupt-bubble__timestamp {
463
+ font-size: 0.6875rem;
464
+ color: var(--flowdrop-interrupt-pending-text-light);
465
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
466
+ }
467
+
468
+ .interrupt-bubble--completed .interrupt-bubble__timestamp {
469
+ color: var(--flowdrop-interrupt-completed-text-light);
470
+ }
471
+
472
+ .interrupt-bubble--cancelled .interrupt-bubble__timestamp {
473
+ color: var(--flowdrop-interrupt-cancelled-text-light);
474
+ }
475
+
476
+ .interrupt-bubble--error .interrupt-bubble__timestamp {
477
+ color: var(--flowdrop-interrupt-error-text-light);
478
+ }
479
+
480
+ /* Error message */
481
+ .interrupt-bubble__error {
482
+ display: flex;
483
+ align-items: center;
484
+ gap: 0.5rem;
485
+ padding: 0.5rem 0.75rem;
486
+ background-color: var(--color-ref-red-50, rgba(239, 68, 68, 0.1));
487
+ border-radius: 0.375rem;
488
+ color: var(--flowdrop-interrupt-error-text);
489
+ font-size: 0.8125rem;
490
+ }
491
+
492
+ .interrupt-bubble__retry-btn {
493
+ display: inline-flex;
494
+ align-items: center;
495
+ gap: 0.25rem;
496
+ margin-left: auto;
497
+ padding: 0.25rem 0.5rem;
498
+ font-size: 0.75rem;
499
+ font-weight: 500;
500
+ font-family: inherit;
501
+ color: #ffffff;
502
+ background-color: var(--flowdrop-interrupt-error-avatar);
503
+ border: none;
504
+ border-radius: 0.25rem;
505
+ cursor: pointer;
506
+ transition: background-color 0.15s ease;
507
+ }
508
+
509
+ .interrupt-bubble__retry-btn:hover {
510
+ background-color: var(--color-ref-red-600, #dc2626);
511
+ }
512
+
513
+ /* Prompt */
514
+ .interrupt-bubble__prompt {
515
+ background-color: var(--flowdrop-interrupt-prompt-bg);
516
+ border-radius: 0.5rem;
517
+ padding: 1rem;
518
+ border: 1px solid var(--flowdrop-interrupt-prompt-border-pending);
519
+ }
520
+
521
+ .interrupt-bubble--completed .interrupt-bubble__prompt {
522
+ border-color: var(--flowdrop-interrupt-prompt-border-completed);
523
+ }
524
+
525
+ .interrupt-bubble--cancelled .interrupt-bubble__prompt {
526
+ border-color: var(--flowdrop-interrupt-prompt-border-cancelled);
527
+ opacity: 0.75;
528
+ }
529
+
530
+ .interrupt-bubble--error .interrupt-bubble__prompt {
531
+ border-color: var(--flowdrop-interrupt-prompt-border-error);
532
+ }
533
+
534
+ /* Cancel button wrapper */
535
+ .interrupt-bubble__cancel-wrapper {
536
+ display: flex;
537
+ justify-content: flex-end;
538
+ }
539
+
540
+ .interrupt-bubble__cancel-btn {
541
+ display: inline-flex;
542
+ align-items: center;
543
+ gap: 0.375rem;
544
+ padding: 0.375rem 0.75rem;
545
+ font-size: 0.75rem;
546
+ font-weight: 500;
547
+ font-family: inherit;
548
+ color: #6b7280;
549
+ background-color: transparent;
550
+ border: 1px solid #d1d5db;
551
+ border-radius: 0.375rem;
552
+ cursor: pointer;
553
+ transition: all 0.15s ease;
554
+ }
555
+
556
+ .interrupt-bubble__cancel-btn:hover:not(:disabled) {
557
+ color: #dc2626;
558
+ border-color: #fca5a5;
559
+ background-color: #fef2f2;
560
+ }
561
+
562
+ .interrupt-bubble__cancel-btn:disabled {
563
+ opacity: 0.5;
564
+ cursor: not-allowed;
565
+ }
566
+
567
+ /* Footer */
568
+ .interrupt-bubble__footer {
569
+ display: flex;
570
+ align-items: center;
571
+ gap: 0.5rem;
572
+ padding-top: 0.5rem;
573
+ border-top: 1px solid var(--flowdrop-interrupt-prompt-border-pending);
574
+ }
575
+
576
+ .interrupt-bubble--completed .interrupt-bubble__footer {
577
+ border-color: var(--flowdrop-interrupt-prompt-border-completed);
578
+ }
579
+
580
+ .interrupt-bubble--cancelled .interrupt-bubble__footer {
581
+ border-color: var(--flowdrop-interrupt-prompt-border-cancelled);
582
+ }
583
+
584
+ .interrupt-bubble--error .interrupt-bubble__footer {
585
+ border-color: var(--flowdrop-interrupt-prompt-border-error);
586
+ }
587
+
588
+ .interrupt-bubble__node {
589
+ display: flex;
590
+ align-items: center;
591
+ gap: 0.25rem;
592
+ font-size: 0.6875rem;
593
+ color: var(--flowdrop-interrupt-pending-text);
594
+ }
595
+
596
+ .interrupt-bubble--completed .interrupt-bubble__node {
597
+ color: var(--flowdrop-interrupt-completed-text);
598
+ }
599
+
600
+ .interrupt-bubble--cancelled .interrupt-bubble__node {
601
+ color: var(--flowdrop-interrupt-cancelled-text);
602
+ }
603
+
604
+ .interrupt-bubble--error .interrupt-bubble__node {
605
+ color: var(--flowdrop-interrupt-error-text);
606
+ }
607
+
608
+ /* Responsive */
609
+ @media (max-width: 640px) {
610
+ .interrupt-bubble {
611
+ margin: 0.5rem;
612
+ padding: 0.875rem 1rem;
613
+ }
614
+
615
+ .interrupt-bubble__avatar {
616
+ width: 2rem;
617
+ height: 2rem;
618
+ font-size: 1rem;
619
+ }
620
+ }
621
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Interrupt } from '../../types/interrupt.js';
2
+ import { type InterruptWithState } from '../../stores/interruptStore.js';
3
+ /**
4
+ * Component props
5
+ */
6
+ interface Props {
7
+ /** The interrupt to display (initial data, used for ID lookup) */
8
+ interrupt: Interrupt | InterruptWithState;
9
+ /** Whether to show the timestamp */
10
+ showTimestamp?: boolean;
11
+ /** Callback to refresh messages after interrupt resolution */
12
+ onResolved?: () => void;
13
+ }
14
+ declare const InterruptBubble: import("svelte").Component<Props, {}, "">;
15
+ type InterruptBubble = ReturnType<typeof InterruptBubble>;
16
+ export default InterruptBubble;