@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.
Files changed (84) hide show
  1. package/dist/api/enhanced-client.d.ts +29 -16
  2. package/dist/api/enhanced-client.js +0 -14
  3. package/dist/components/ConfigForm.svelte +1 -0
  4. package/dist/components/PipelineStatus.svelte +9 -12
  5. package/dist/components/SchemaForm.svelte +1 -0
  6. package/dist/components/WorkflowEditor.svelte +3 -0
  7. package/dist/components/form/FormAutocomplete.svelte +67 -10
  8. package/dist/components/form/FormField.svelte +21 -0
  9. package/dist/components/form/FormFieldLight.svelte +1 -0
  10. package/dist/components/interrupt/ChoicePrompt.svelte +24 -5
  11. package/dist/components/interrupt/ConfirmationPrompt.svelte +5 -0
  12. package/dist/components/interrupt/InterruptBubble.svelte +88 -17
  13. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
  14. package/dist/components/interrupt/ReviewPrompt.svelte +20 -0
  15. package/dist/components/interrupt/TextInputPrompt.svelte +5 -0
  16. package/dist/components/nodes/GatewayNode.svelte +2 -6
  17. package/dist/components/nodes/WorkflowNode.svelte +2 -6
  18. package/dist/components/playground/ChatBubble.svelte +289 -0
  19. package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
  20. package/dist/components/playground/ChatInput.svelte +359 -0
  21. package/dist/components/playground/ChatInput.svelte.d.ts +14 -0
  22. package/dist/components/playground/ChatPanel.svelte +100 -724
  23. package/dist/components/playground/ChatPanel.svelte.d.ts +9 -26
  24. package/dist/components/playground/ControlPanel.svelte +496 -0
  25. package/dist/components/playground/ControlPanel.svelte.d.ts +20 -0
  26. package/dist/components/playground/ExecutionConsole.svelte +163 -0
  27. package/dist/components/playground/ExecutionConsole.svelte.d.ts +14 -0
  28. package/dist/components/playground/HierarchyTrail.svelte +88 -0
  29. package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
  30. package/dist/components/playground/LogRow.svelte +178 -0
  31. package/dist/components/playground/LogRow.svelte.d.ts +8 -0
  32. package/dist/components/playground/MessageBubble.stories.svelte +89 -0
  33. package/dist/components/playground/MessageBubble.svelte +25 -737
  34. package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
  35. package/dist/components/playground/MessageCard.svelte +106 -0
  36. package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
  37. package/dist/components/playground/MessageMarkdown.svelte +160 -0
  38. package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
  39. package/dist/components/playground/MessageNotice.svelte +120 -0
  40. package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
  41. package/dist/components/playground/MessageStream.svelte +367 -0
  42. package/dist/components/playground/MessageStream.svelte.d.ts +27 -0
  43. package/dist/components/playground/MessageTagChip.svelte +99 -0
  44. package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
  45. package/dist/components/playground/MessageTagStrip.svelte +37 -0
  46. package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
  47. package/dist/components/playground/PipelineKanbanView.svelte +284 -0
  48. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +11 -0
  49. package/dist/components/playground/PipelinePanel.svelte +204 -65
  50. package/dist/components/playground/PipelinePanel.svelte.d.ts +3 -1
  51. package/dist/components/playground/PipelineTableView.svelte +376 -0
  52. package/dist/components/playground/PipelineTableView.svelte.d.ts +11 -0
  53. package/dist/components/playground/Playground.svelte +262 -1200
  54. package/dist/components/playground/Playground.svelte.d.ts +0 -13
  55. package/dist/components/playground/PlaygroundStudio.svelte +113 -61
  56. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -1
  57. package/dist/components/playground/messageDisplay.d.ts +19 -0
  58. package/dist/components/playground/messageDisplay.js +62 -0
  59. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +22 -0
  60. package/dist/components/playground/pipelineViewUtils.svelte.js +77 -0
  61. package/dist/form/autocomplete.d.ts +1 -0
  62. package/dist/form/autocomplete.js +1 -0
  63. package/dist/form/index.d.ts +17 -0
  64. package/dist/form/index.js +19 -0
  65. package/dist/messages/defaults.d.ts +29 -0
  66. package/dist/messages/defaults.js +30 -0
  67. package/dist/playground/index.d.ts +6 -1
  68. package/dist/playground/index.js +6 -0
  69. package/dist/playground/mount.d.ts +3 -0
  70. package/dist/playground/mount.js +3 -2
  71. package/dist/schemas/v1/workflow.schema.json +10 -1
  72. package/dist/services/categoriesApi.d.ts +2 -1
  73. package/dist/services/categoriesApi.js +5 -3
  74. package/dist/services/portConfigApi.d.ts +2 -1
  75. package/dist/services/portConfigApi.js +5 -3
  76. package/dist/stores/playgroundStore.svelte.d.ts +6 -0
  77. package/dist/stores/playgroundStore.svelte.js +21 -1
  78. package/dist/svelte-app.d.ts +1 -0
  79. package/dist/svelte-app.js +5 -5
  80. package/dist/types/index.d.ts +41 -2
  81. package/dist/types/playground.d.ts +81 -2
  82. package/dist/types/playground.js +19 -7
  83. package/dist/utils/nodeStatus.js +15 -5
  84. package/package.json +6 -1
@@ -0,0 +1,367 @@
1
+ <!--
2
+ MessageStream Component
3
+
4
+ Renders the playground message feed with interrupt UI inline. No input area.
5
+ This is the shared primitive used by ChatPanel (conversational) and
6
+ ExecutionConsole (workflow runtime surface).
7
+
8
+ The empty/welcome state is delegated to consumers via the `welcome` and
9
+ `emptySession` snippets so each wrapper renders context-appropriate copy.
10
+ -->
11
+
12
+ <script lang="ts">
13
+ import { tick, untrack, type Snippet } from 'svelte';
14
+ import MessageBubble from './MessageBubble.svelte';
15
+ import { InterruptBubble } from '../interrupt/index.js';
16
+ import type { PlaygroundMessage } from '../../types/playground.js';
17
+ import {
18
+ isInterruptMetadata,
19
+ extractInterruptMetadata,
20
+ metadataToInterrupt
21
+ } from '../../types/interrupt.js';
22
+ import {
23
+ getMessages,
24
+ getChatMessages,
25
+ getIsExecuting,
26
+ getCurrentSession,
27
+ getShowLogs
28
+ } from '../../stores/playgroundStore.svelte.js';
29
+ import {
30
+ getInterruptsMap,
31
+ interruptActions,
32
+ getInterruptByMessageId
33
+ } from '../../stores/interruptStore.svelte.js';
34
+ import { m } from '../../messages/index.js';
35
+
36
+ interface Props {
37
+ /** Whether to show timestamps on messages */
38
+ showTimestamps?: boolean;
39
+ /** Whether to auto-scroll to bottom on new messages */
40
+ autoScroll?: boolean;
41
+ /** Whether to enable markdown rendering in messages */
42
+ enableMarkdown?: boolean;
43
+ /**
44
+ * Whether this surface is permitted to show log messages.
45
+ * When true, the store's showLogs toggle takes effect.
46
+ * When false (default), only chat messages are ever shown regardless of the toggle.
47
+ * Set to true on execution surfaces (e.g. ExecutionConsole); leave false on pure chat surfaces.
48
+ */
49
+ allowLogs?: boolean;
50
+ /** Render system messages in compact inline form */
51
+ compactSystemMessages?: boolean;
52
+ /** Called when an interrupt is resolved */
53
+ onInterruptResolved?: () => void;
54
+ /** Custom render for the no-session welcome state */
55
+ welcome?: Snippet;
56
+ /** Custom render for the empty-session state */
57
+ emptySession?: Snippet;
58
+ }
59
+
60
+ let {
61
+ showTimestamps = true,
62
+ autoScroll = true,
63
+ enableMarkdown = true,
64
+ allowLogs = false,
65
+ compactSystemMessages = true,
66
+ onInterruptResolved,
67
+ welcome,
68
+ emptySession
69
+ }: Props = $props();
70
+
71
+ const states = $derived(m().playground.states);
72
+
73
+ /** Reference to the messages container for scrolling */
74
+ let messagesContainer: HTMLDivElement | undefined;
75
+
76
+ const displayMessages = $derived(
77
+ allowLogs && getShowLogs() ? getMessages() : getChatMessages()
78
+ );
79
+
80
+ let previousMessageCount = 0;
81
+ let userScrolledUp = false;
82
+
83
+ function handleScroll() {
84
+ if (!messagesContainer) return;
85
+ const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
86
+ userScrolledUp = scrollHeight - scrollTop - clientHeight > 50;
87
+ }
88
+
89
+ function isFormFocused(): boolean {
90
+ if (!messagesContainer) return false;
91
+ const activeElement = document.activeElement;
92
+ if (!activeElement) return false;
93
+ const isFormControl =
94
+ activeElement.tagName === 'INPUT' ||
95
+ activeElement.tagName === 'TEXTAREA' ||
96
+ activeElement.tagName === 'SELECT' ||
97
+ activeElement.tagName === 'BUTTON' ||
98
+ activeElement.getAttribute('contenteditable') === 'true';
99
+ return isFormControl && messagesContainer.contains(activeElement);
100
+ }
101
+
102
+ function isInterruptMessage(message: PlaygroundMessage): boolean {
103
+ return isInterruptMetadata(message.metadata as Record<string, unknown> | undefined);
104
+ }
105
+
106
+ /**
107
+ * Sync interrupt messages into the interrupt store. Runs in an effect to
108
+ * avoid Svelte 5's state_unsafe_mutation error during render.
109
+ */
110
+ $effect(() => {
111
+ const interruptMessages = displayMessages.filter(isInterruptMessage);
112
+
113
+ for (const message of interruptMessages) {
114
+ const existing = getInterruptByMessageId(message.id);
115
+ if (!existing) {
116
+ const metadata = extractInterruptMetadata(
117
+ message.metadata as Record<string, unknown> | undefined
118
+ );
119
+ if (metadata) {
120
+ const interrupt = metadataToInterrupt(metadata, message.id, message.content);
121
+ interruptActions.addInterrupt(interrupt);
122
+
123
+ if (message.status === 'completed') {
124
+ interruptActions.resolveInterrupt(interrupt.id, metadata.response_value);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ });
130
+
131
+ const interruptsByMessageId = $derived(
132
+ new Map(
133
+ Array.from(getInterruptsMap().values())
134
+ .filter((i) => i.messageId)
135
+ .map((i) => [i.messageId, i])
136
+ )
137
+ );
138
+
139
+ function getInterruptForMessage(message: PlaygroundMessage) {
140
+ return interruptsByMessageId.get(message.id);
141
+ }
142
+
143
+ const showWelcome = $derived(!getCurrentSession() && displayMessages.length === 0);
144
+ const showEmptyChat = $derived(getCurrentSession() !== null && displayMessages.length === 0);
145
+
146
+ // Reset scroll-tracking when session changes
147
+ $effect(() => {
148
+ if (getCurrentSession()) {
149
+ userScrolledUp = false;
150
+ }
151
+ });
152
+
153
+ $effect(() => {
154
+ const currentCount = displayMessages.length;
155
+
156
+ if (!autoScroll || !messagesContainer) {
157
+ untrack(() => { previousMessageCount = currentCount; });
158
+ return;
159
+ }
160
+
161
+ const hasNewMessage = currentCount > previousMessageCount;
162
+ untrack(() => { previousMessageCount = currentCount; });
163
+
164
+ if (!hasNewMessage || userScrolledUp || isFormFocused()) return;
165
+
166
+ tick().then(() => {
167
+ if (messagesContainer) {
168
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
169
+ }
170
+ });
171
+ });
172
+ </script>
173
+
174
+ <div class="message-stream" role="log" aria-label={m().playground.controlPanel.messageStreamLabel} bind:this={messagesContainer} onscroll={handleScroll}>
175
+ {#if showWelcome}
176
+ {#if welcome}
177
+ {@render welcome()}
178
+ {/if}
179
+ {:else if showEmptyChat}
180
+ {#if emptySession}
181
+ {@render emptySession()}
182
+ {/if}
183
+ {:else}
184
+ {#each displayMessages as message, index (message.id)}
185
+ {#if isInterruptMessage(message)}
186
+ {@const interrupt = getInterruptForMessage(message)}
187
+ {#if interrupt}
188
+ <InterruptBubble
189
+ {interrupt}
190
+ showTimestamp={showTimestamps}
191
+ onResolved={onInterruptResolved}
192
+ hierarchy={message.hierarchy}
193
+ tags={message.tags}
194
+ />
195
+ {/if}
196
+ {:else}
197
+ <MessageBubble
198
+ {message}
199
+ showTimestamp={showTimestamps}
200
+ isLast={index === displayMessages.length - 1}
201
+ {enableMarkdown}
202
+ {compactSystemMessages}
203
+ />
204
+ {/if}
205
+ {/each}
206
+
207
+ {#if getIsExecuting()}
208
+ <div class="message-stream__typing">
209
+ <div class="message-stream__typing-indicator">
210
+ <span></span>
211
+ <span></span>
212
+ <span></span>
213
+ </div>
214
+ <span class="message-stream__typing-text">{states.processing}</span>
215
+ </div>
216
+ {/if}
217
+ {/if}
218
+ </div>
219
+
220
+ <style>
221
+ .message-stream {
222
+ flex: 1;
223
+ min-height: 0;
224
+ overflow-y: auto;
225
+ padding: var(--fd-space-3xl);
226
+
227
+ /* Establish a containment context so message rows can adapt to the
228
+ stream's actual width (not the viewport's). The matching @container
229
+ queries (for .log-row) live below in the same <style> block, so
230
+ renaming the container only requires editing this file. */
231
+ container-type: inline-size;
232
+ container-name: fd-message-stream;
233
+ }
234
+
235
+ /* Shared fade-in for newly-appended message rows. `-global-` so
236
+ ChatBubble.svelte / MessageCard.svelte can reference it without
237
+ redeclaring. Honour reduced-motion in the same place. */
238
+ @keyframes -global-fd-fade-in {
239
+ from {
240
+ opacity: 0;
241
+ transform: translateY(6px);
242
+ }
243
+ to {
244
+ opacity: 1;
245
+ transform: translateY(0);
246
+ }
247
+ }
248
+
249
+ @media (prefers-reduced-motion: reduce) {
250
+ :global(.message-bubble),
251
+ :global(.message-card) {
252
+ animation: none;
253
+ }
254
+ }
255
+
256
+ /* Container-query reshaping for log rows. Lives next to the
257
+ container-name declaration so the coupling is local — selectors are
258
+ :global because .log-row is a sibling component's class.
259
+
260
+ Tier 1 (≤720px): two rows — level/body, then tags/timestamp.
261
+ Tier 2 (≤480px): collapse further; body forces internal line break. */
262
+ @container fd-message-stream (max-width: 720px) {
263
+ :global(.log-row) {
264
+ display: grid;
265
+ grid-template-columns: auto 1fr auto;
266
+ grid-template-areas:
267
+ "level body body"
268
+ ". tags timestamp";
269
+ align-items: baseline;
270
+ row-gap: var(--fd-space-2xs);
271
+ column-gap: var(--fd-space-sm);
272
+ }
273
+ :global(.log-row__level) { grid-area: level; }
274
+ :global(.log-row__body) { grid-area: body; min-width: 0; }
275
+ :global(.log-row__tags) { grid-area: tags; justify-self: start; }
276
+ :global(.log-row__timestamp) { grid-area: timestamp; justify-self: end; }
277
+ }
278
+
279
+ @container fd-message-stream (max-width: 480px) {
280
+ :global(.log-row) {
281
+ grid-template-columns: auto 1fr;
282
+ grid-template-areas:
283
+ "level body"
284
+ ". tags";
285
+ }
286
+ :global(.log-row__text) {
287
+ flex-basis: 100%;
288
+ min-width: 0;
289
+ }
290
+ :global(.log-row__timestamp) {
291
+ display: none;
292
+ }
293
+ /* Drop the source + node chips: source is implied by the level
294
+ colour, node duplicates the hierarchy trail's last entry. Keeping
295
+ them at this width forced each chip onto its own line and made
296
+ log rows 5–6 lines tall. */
297
+ :global(.log-row__source),
298
+ :global(.log-row__node) {
299
+ display: none;
300
+ }
301
+ /* Reclaim horizontal room by tightening the row's own padding —
302
+ can't shrink the stream's padding from inside its own
303
+ container query. */
304
+ :global(.log-row) {
305
+ padding-left: var(--fd-space-xs);
306
+ padding-right: var(--fd-space-xs);
307
+ }
308
+ }
309
+
310
+ .message-stream__typing {
311
+ display: flex;
312
+ align-items: center;
313
+ gap: var(--fd-space-xs);
314
+ padding: var(--fd-space-md) var(--fd-space-xl);
315
+ margin-top: var(--fd-space-xs);
316
+ background-color: var(--fd-muted);
317
+ border-radius: var(--fd-radius-2xl);
318
+ width: fit-content;
319
+ }
320
+
321
+ .message-stream__typing-indicator {
322
+ display: flex;
323
+ gap: var(--fd-space-3xs);
324
+ }
325
+
326
+ .message-stream__typing-indicator span {
327
+ width: var(--fd-space-2xs);
328
+ height: var(--fd-space-2xs);
329
+ background-color: var(--fd-muted-foreground);
330
+ border-radius: var(--fd-radius-full);
331
+ animation: message-stream-bounce 1.4s ease-in-out infinite;
332
+ }
333
+
334
+ .message-stream__typing-indicator span:nth-child(1) {
335
+ animation-delay: 0s;
336
+ }
337
+
338
+ .message-stream__typing-indicator span:nth-child(2) {
339
+ animation-delay: 0.2s;
340
+ }
341
+
342
+ .message-stream__typing-indicator span:nth-child(3) {
343
+ animation-delay: 0.4s;
344
+ }
345
+
346
+ @keyframes message-stream-bounce {
347
+ 0%,
348
+ 60%,
349
+ 100% {
350
+ transform: translateY(0);
351
+ }
352
+ 30% {
353
+ transform: translateY(-0.25rem);
354
+ }
355
+ }
356
+
357
+ .message-stream__typing-text {
358
+ font-size: var(--fd-text-sm);
359
+ color: var(--fd-muted-foreground);
360
+ }
361
+
362
+ @media (max-width: 640px) {
363
+ .message-stream {
364
+ padding: var(--fd-space-md) 0;
365
+ }
366
+ }
367
+ </style>
@@ -0,0 +1,27 @@
1
+ import { type Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Whether to show timestamps on messages */
4
+ showTimestamps?: boolean;
5
+ /** Whether to auto-scroll to bottom on new messages */
6
+ autoScroll?: boolean;
7
+ /** Whether to enable markdown rendering in messages */
8
+ enableMarkdown?: boolean;
9
+ /**
10
+ * Whether this surface is permitted to show log messages.
11
+ * When true, the store's showLogs toggle takes effect.
12
+ * When false (default), only chat messages are ever shown regardless of the toggle.
13
+ * Set to true on execution surfaces (e.g. ExecutionConsole); leave false on pure chat surfaces.
14
+ */
15
+ allowLogs?: boolean;
16
+ /** Render system messages in compact inline form */
17
+ compactSystemMessages?: boolean;
18
+ /** Called when an interrupt is resolved */
19
+ onInterruptResolved?: () => void;
20
+ /** Custom render for the no-session welcome state */
21
+ welcome?: Snippet;
22
+ /** Custom render for the empty-session state */
23
+ emptySession?: Snippet;
24
+ }
25
+ declare const MessageStream: import("svelte").Component<Props, {}, "">;
26
+ type MessageStream = ReturnType<typeof MessageStream>;
27
+ export default MessageStream;
@@ -0,0 +1,99 @@
1
+ <!--
2
+ MessageTagChip Component
3
+
4
+ Renders a single server-emitted MessageTag as a compact chip. Semantic
5
+ color comes from tag.color, visual emphasis from tag.variant. Used by
6
+ MessageBubble and InterruptBubble.
7
+
8
+ Styling: a single base rule reads from CSS custom properties; one rule
9
+ per color sets --chip-c, one rule per variant sets bg/fg/border in terms
10
+ of --chip-c. Adding a color is one line.
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import Icon from '@iconify/svelte';
15
+ import type { MessageTag } from '../../types/playground.js';
16
+
17
+ interface Props {
18
+ tag: MessageTag;
19
+ }
20
+
21
+ let { tag }: Props = $props();
22
+
23
+ const color = $derived(tag.color ?? 'muted');
24
+ const variant = $derived(tag.variant ?? 'subtle');
25
+ </script>
26
+
27
+ <span
28
+ class="message-tag-chip"
29
+ data-color={color}
30
+ data-variant={variant}
31
+ aria-label={tag.type ? `${tag.type}: ${tag.label}` : undefined}
32
+ >
33
+ {#if tag.icon}
34
+ <Icon icon={tag.icon} class="message-tag-chip__icon" aria-hidden="true" />
35
+ {/if}
36
+ <span class="message-tag-chip__label">{tag.label}</span>
37
+ </span>
38
+
39
+ <style>
40
+ .message-tag-chip {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: var(--fd-space-3xs);
44
+ padding: 0 var(--fd-space-3xs);
45
+ border-radius: var(--fd-radius-sm);
46
+ font-family: var(--fd-font-mono);
47
+ font-size: var(--fd-text-2xs);
48
+ line-height: 1.4;
49
+ white-space: nowrap;
50
+ min-width: 0;
51
+ max-width: 100%;
52
+ background-color: var(--chip-bg);
53
+ color: var(--chip-fg);
54
+ border: 1px solid var(--chip-border, transparent);
55
+ }
56
+
57
+ .message-tag-chip__label {
58
+ overflow: hidden;
59
+ text-overflow: ellipsis;
60
+ }
61
+
62
+ .message-tag-chip :global(.message-tag-chip__icon) {
63
+ flex-shrink: 0;
64
+ font-size: 0.875em;
65
+ opacity: 0.8;
66
+ }
67
+
68
+ /* Color hooks — one line per color. To add a color, add a row here. */
69
+ .message-tag-chip[data-color='muted'] { --chip-c: var(--fd-muted-foreground); --chip-c-on: var(--fd-background); }
70
+ .message-tag-chip[data-color='primary'] { --chip-c: var(--fd-primary); --chip-c-on: var(--fd-primary-foreground); }
71
+ .message-tag-chip[data-color='success'] { --chip-c: var(--fd-success, oklch(55% 0.15 145)); --chip-c-on: white; }
72
+ .message-tag-chip[data-color='warning'] { --chip-c: var(--fd-warning); --chip-c-on: var(--fd-background); }
73
+ .message-tag-chip[data-color='error'] { --chip-c: var(--fd-error); --chip-c-on: white; }
74
+ .message-tag-chip[data-color='info'] { --chip-c: var(--fd-info); --chip-c-on: var(--fd-background); }
75
+
76
+ /* Variants — derive bg/fg/border from --chip-c. */
77
+ .message-tag-chip[data-variant='subtle'] {
78
+ --chip-bg: color-mix(in srgb, var(--chip-c) 14%, transparent);
79
+ --chip-fg: var(--chip-c);
80
+ }
81
+ .message-tag-chip[data-variant='subtle'][data-color='muted'] {
82
+ /* Muted is the only color we render against the design's --fd-muted
83
+ surface for legibility; the color-mix path would lose contrast. */
84
+ --chip-bg: var(--fd-muted);
85
+ --chip-fg: var(--fd-muted-foreground);
86
+ }
87
+ .message-tag-chip[data-variant='solid'] {
88
+ --chip-bg: var(--chip-c);
89
+ --chip-fg: var(--chip-c-on);
90
+ }
91
+ .message-tag-chip[data-variant='outline'] {
92
+ --chip-bg: transparent;
93
+ --chip-fg: var(--chip-c);
94
+ --chip-border: var(--chip-c);
95
+ }
96
+ .message-tag-chip[data-variant='outline'][data-color='muted'] {
97
+ --chip-border: var(--fd-border);
98
+ }
99
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { MessageTag } from '../../types/playground.js';
2
+ interface Props {
3
+ tag: MessageTag;
4
+ }
5
+ declare const MessageTagChip: import("svelte").Component<Props, {}, "">;
6
+ type MessageTagChip = ReturnType<typeof MessageTagChip>;
7
+ export default MessageTagChip;
@@ -0,0 +1,37 @@
1
+ <!--
2
+ MessageTagStrip Component
3
+
4
+ A flex-wrapped row of MessageTagChips. Encapsulates the layout the four
5
+ message variants used to copy individually.
6
+ -->
7
+
8
+ <script lang="ts">
9
+ import type { MessageTag } from '../../types/playground.js';
10
+ import MessageTagChip from './MessageTagChip.svelte';
11
+
12
+ interface Props {
13
+ tags: MessageTag[];
14
+ }
15
+
16
+ let { tags }: Props = $props();
17
+ </script>
18
+
19
+ {#if tags.length > 0}
20
+ <!-- No role/label — each chip is already labelled (via tag.type when set)
21
+ and a one-element group adds nothing for AT users. -->
22
+ <span class="message-tag-strip">
23
+ {#each tags as tag (tag.id)}
24
+ <MessageTagChip {tag} />
25
+ {/each}
26
+ </span>
27
+ {/if}
28
+
29
+ <style>
30
+ .message-tag-strip {
31
+ display: inline-flex;
32
+ flex-wrap: wrap;
33
+ align-items: center;
34
+ gap: var(--fd-space-2xs);
35
+ min-width: 0;
36
+ }
37
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { MessageTag } from '../../types/playground.js';
2
+ interface Props {
3
+ tags: MessageTag[];
4
+ }
5
+ declare const MessageTagStrip: import("svelte").Component<Props, {}, "">;
6
+ type MessageTagStrip = ReturnType<typeof MessageTagStrip>;
7
+ export default MessageTagStrip;