@flowdrop/flowdrop 1.12.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 (47) hide show
  1. package/dist/components/ConfigForm.svelte +1 -0
  2. package/dist/components/SchemaForm.svelte +1 -0
  3. package/dist/components/form/FormAutocomplete.svelte +67 -10
  4. package/dist/components/form/FormField.svelte +21 -0
  5. package/dist/components/form/FormFieldLight.svelte +1 -0
  6. package/dist/components/interrupt/InterruptBubble.svelte +76 -17
  7. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
  8. package/dist/components/playground/ChatBubble.svelte +289 -0
  9. package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
  10. package/dist/components/playground/HierarchyTrail.svelte +88 -0
  11. package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
  12. package/dist/components/playground/LogRow.svelte +178 -0
  13. package/dist/components/playground/LogRow.svelte.d.ts +8 -0
  14. package/dist/components/playground/MessageBubble.stories.svelte +89 -0
  15. package/dist/components/playground/MessageBubble.svelte +25 -737
  16. package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
  17. package/dist/components/playground/MessageCard.svelte +106 -0
  18. package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
  19. package/dist/components/playground/MessageMarkdown.svelte +160 -0
  20. package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
  21. package/dist/components/playground/MessageNotice.svelte +120 -0
  22. package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
  23. package/dist/components/playground/MessageStream.svelte +85 -1
  24. package/dist/components/playground/MessageTagChip.svelte +99 -0
  25. package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
  26. package/dist/components/playground/MessageTagStrip.svelte +37 -0
  27. package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
  28. package/dist/components/playground/PlaygroundStudio.svelte +78 -0
  29. package/dist/components/playground/messageDisplay.d.ts +19 -0
  30. package/dist/components/playground/messageDisplay.js +62 -0
  31. package/dist/form/autocomplete.d.ts +1 -0
  32. package/dist/form/autocomplete.js +1 -0
  33. package/dist/form/index.d.ts +17 -0
  34. package/dist/form/index.js +19 -0
  35. package/dist/messages/defaults.d.ts +5 -0
  36. package/dist/messages/defaults.js +6 -0
  37. package/dist/schemas/v1/workflow.schema.json +10 -1
  38. package/dist/services/categoriesApi.d.ts +2 -1
  39. package/dist/services/categoriesApi.js +5 -3
  40. package/dist/services/portConfigApi.d.ts +2 -1
  41. package/dist/services/portConfigApi.js +5 -3
  42. package/dist/svelte-app.d.ts +1 -0
  43. package/dist/svelte-app.js +5 -5
  44. package/dist/types/index.d.ts +13 -0
  45. package/dist/types/playground.d.ts +76 -0
  46. package/dist/types/playground.js +14 -0
  47. package/package.json +6 -1
@@ -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;
@@ -156,6 +156,15 @@
156
156
  {@const executions = getCurrentSession()?.executions ?? []}
157
157
 
158
158
  <div class="playground-studio__pipeline" style="width: {pipelineWidth}px;">
159
+ <button
160
+ type="button"
161
+ class="playground-studio__back-to-chat"
162
+ aria-label="Back to chat"
163
+ onclick={pipelinePanelActions.toggle}
164
+ >
165
+ <Icon icon="mdi:arrow-left" aria-hidden="true" />
166
+ <span>Back to chat</span>
167
+ </button>
159
168
  <PipelinePanel
160
169
  pipelineId={activeId}
161
170
  workflow={resolvedWorkflow}
@@ -254,6 +263,13 @@
254
263
  .playground-studio__pipeline {
255
264
  overflow: hidden;
256
265
  flex-shrink: 0;
266
+ position: relative;
267
+ }
268
+
269
+ /* Mobile-only "back to chat" affordance. Hidden on wider viewports where
270
+ the ControlPanel's pipeline toggle remains reachable. */
271
+ .playground-studio__back-to-chat {
272
+ display: none;
257
273
  }
258
274
 
259
275
  /* Drag handle between the two panes */
@@ -375,4 +391,66 @@
375
391
  .playground-studio__retry-btn:hover {
376
392
  background-color: var(--fd-primary-hover);
377
393
  }
394
+
395
+ /* ============================================================
396
+ Mobile layout (< 768px)
397
+ Switch from side-by-side panes to one-at-a-time fullscreen.
398
+ The pipeline panel, when open, covers the chat. Users toggle
399
+ between them via the pipeline panel button. The resizer is
400
+ hidden — at this width there's nothing to resize.
401
+ ============================================================ */
402
+ @media (max-width: 768px) {
403
+ .playground-studio__pipeline {
404
+ /* Override the JS-driven width — take the whole row */
405
+ width: 100% !important;
406
+ flex: 1 1 auto;
407
+ display: flex;
408
+ flex-direction: column;
409
+ }
410
+
411
+ .playground-studio__resizer {
412
+ display: none;
413
+ }
414
+
415
+ /* When pipeline is open (chat is NOT solo), hide chat to give the
416
+ pipeline the full viewport. When pipeline closes, chat goes back
417
+ to full-width via the existing --solo class. */
418
+ .playground-studio__chat:not(.playground-studio__chat--solo) {
419
+ display: none;
420
+ }
421
+
422
+ .playground-studio__back-to-chat {
423
+ display: inline-flex;
424
+ align-items: center;
425
+ gap: var(--fd-space-2xs);
426
+ align-self: flex-start;
427
+ margin: var(--fd-space-xs);
428
+ padding: var(--fd-space-2xs) var(--fd-space-md);
429
+ min-height: 2.5rem;
430
+ font-size: var(--fd-text-sm);
431
+ font-weight: 500;
432
+ font-family: inherit;
433
+ color: var(--fd-foreground);
434
+ background-color: var(--fd-card);
435
+ border: 1px solid var(--fd-border);
436
+ border-radius: var(--fd-radius-md);
437
+ cursor: pointer;
438
+ transition: background-color var(--fd-transition-fast);
439
+ }
440
+
441
+ .playground-studio__back-to-chat:hover {
442
+ background-color: var(--fd-muted);
443
+ }
444
+
445
+ .playground-studio__back-to-chat:focus-visible {
446
+ outline: 2px solid var(--fd-ring);
447
+ outline-offset: 2px;
448
+ }
449
+ }
450
+
451
+ @media (prefers-reduced-motion: reduce) {
452
+ .playground-studio__back-to-chat {
453
+ transition: none;
454
+ }
455
+ }
378
456
  </style>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared formatters / icon maps / label resolver for the message layout
3
+ * components. Pure functions only — i18n strings come in via the `roles`
4
+ * argument; the helper has no runtime dependency on the messages context.
5
+ */
6
+ import type { PlaygroundMessage, PlaygroundMessageLevel, PlaygroundMessageRole } from '../../types/playground.js';
7
+ import type { Messages } from '../../messages/types.js';
8
+ export type RoleLabels = Messages['playground']['roles'];
9
+ export declare function formatTimestamp(timestamp: string): string;
10
+ export declare function formatDuration(ms: number): string;
11
+ export declare function getLogLevelIcon(level: PlaygroundMessageLevel | undefined): string;
12
+ export declare function getRoleIcon(role: PlaygroundMessageRole): string;
13
+ /**
14
+ * Localised author label. Backend-supplied overrides win:
15
+ * - user → metadata.userName (display name)
16
+ * - log → metadata.nodeLabel (human-readable node label)
17
+ * Anything else returns the role's i18n default.
18
+ */
19
+ export declare function getRoleLabel(message: Pick<PlaygroundMessage, 'role' | 'metadata'>, roles: RoleLabels): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared formatters / icon maps / label resolver for the message layout
3
+ * components. Pure functions only — i18n strings come in via the `roles`
4
+ * argument; the helper has no runtime dependency on the messages context.
5
+ */
6
+ export function formatTimestamp(timestamp) {
7
+ return new Date(timestamp).toLocaleTimeString('en-US', {
8
+ hour12: false,
9
+ hour: '2-digit',
10
+ minute: '2-digit',
11
+ second: '2-digit'
12
+ });
13
+ }
14
+ export function formatDuration(ms) {
15
+ return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(2)}s`;
16
+ }
17
+ export function getLogLevelIcon(level) {
18
+ switch (level) {
19
+ case 'error':
20
+ return 'mdi:alert-circle';
21
+ case 'warning':
22
+ return 'mdi:alert';
23
+ case 'debug':
24
+ return 'mdi:bug';
25
+ default:
26
+ return 'mdi:information';
27
+ }
28
+ }
29
+ export function getRoleIcon(role) {
30
+ switch (role) {
31
+ case 'user':
32
+ return 'mdi:account';
33
+ case 'assistant':
34
+ return 'mdi:robot';
35
+ case 'system':
36
+ return 'mdi:cog';
37
+ case 'log':
38
+ return 'mdi:console';
39
+ default:
40
+ return 'mdi:message';
41
+ }
42
+ }
43
+ /**
44
+ * Localised author label. Backend-supplied overrides win:
45
+ * - user → metadata.userName (display name)
46
+ * - log → metadata.nodeLabel (human-readable node label)
47
+ * Anything else returns the role's i18n default.
48
+ */
49
+ export function getRoleLabel(message, roles) {
50
+ switch (message.role) {
51
+ case 'user':
52
+ return message.metadata?.userName ?? roles.you;
53
+ case 'assistant':
54
+ return roles.assistant;
55
+ case 'system':
56
+ return roles.system;
57
+ case 'log':
58
+ return message.metadata?.nodeLabel ?? roles.log;
59
+ default:
60
+ return roles.message;
61
+ }
62
+ }
@@ -0,0 +1 @@
1
+ export { default as FormAutocomplete } from '../components/form/FormAutocomplete.svelte';
@@ -0,0 +1 @@
1
+ export { default as FormAutocomplete } from '../components/form/FormAutocomplete.svelte';
@@ -75,3 +75,20 @@ export type { FieldSchema, FieldType, FieldFormat, FieldOption, OneOfItem, Schem
75
75
  export { isFieldOptionArray, isOneOfArray, normalizeOptions, oneOfToOptions, getSchemaOptions } from '../components/form/types.js';
76
76
  export { fieldComponentRegistry, hiddenFieldMatcher, checkboxGroupMatcher, enumSelectMatcher, textareaMatcher, rangeMatcher, textFieldMatcher, numberFieldMatcher, toggleMatcher, selectOptionsMatcher, arrayMatcher } from './fieldRegistry.js';
77
77
  export type { FieldComponentProps, FieldMatcher, FieldMatcherRegistration, FieldComponent, FieldComponentRegistration } from './fieldRegistry.js';
78
+ /**
79
+ * Use with Svelte's `getContext` inside custom field components registered
80
+ * via `fieldComponentRegistry` to read sibling form field values.
81
+ *
82
+ * @example
83
+ * ```svelte
84
+ * <script lang="ts">
85
+ * import { getContext } from 'svelte';
86
+ * import { FORM_VALUES_KEY, type FormValuesGetter } from '@flowdrop/flowdrop/form';
87
+ *
88
+ * const getFormValues = getContext<FormValuesGetter | undefined>(FORM_VALUES_KEY);
89
+ * const account = $derived(getFormValues?.()['account']);
90
+ * </script>
91
+ * ```
92
+ */
93
+ export declare const FORM_VALUES_KEY: "flowdrop:getFormValues";
94
+ export type FormValuesGetter = () => Record<string, unknown>;
@@ -89,3 +89,22 @@ export {
89
89
  fieldComponentRegistry,
90
90
  // Built-in matchers for custom components
91
91
  hiddenFieldMatcher, checkboxGroupMatcher, enumSelectMatcher, textareaMatcher, rangeMatcher, textFieldMatcher, numberFieldMatcher, toggleMatcher, selectOptionsMatcher, arrayMatcher } from './fieldRegistry.js';
92
+ // ============================================================================
93
+ // Context keys (for custom field components)
94
+ // ============================================================================
95
+ /**
96
+ * Use with Svelte's `getContext` inside custom field components registered
97
+ * via `fieldComponentRegistry` to read sibling form field values.
98
+ *
99
+ * @example
100
+ * ```svelte
101
+ * <script lang="ts">
102
+ * import { getContext } from 'svelte';
103
+ * import { FORM_VALUES_KEY, type FormValuesGetter } from '@flowdrop/flowdrop/form';
104
+ *
105
+ * const getFormValues = getContext<FormValuesGetter | undefined>(FORM_VALUES_KEY);
106
+ * const account = $derived(getFormValues?.()['account']);
107
+ * </script>
108
+ * ```
109
+ */
110
+ export const FORM_VALUES_KEY = 'flowdrop:getFormValues';
@@ -310,6 +310,11 @@ export declare const defaultMessages: {
310
310
  }) => string;
311
311
  readonly executionDuration: "Execution duration";
312
312
  };
313
+ readonly messageAnnotations: {
314
+ readonly hierarchyOf: ({ path }: {
315
+ path: string;
316
+ }) => string;
317
+ };
313
318
  readonly sessions: {
314
319
  readonly header: "Sessions";
315
320
  readonly newSession: "New Session";
@@ -288,6 +288,12 @@ export const defaultMessages = {
288
288
  nodeId: ({ id }) => `Node ID: ${id}`,
289
289
  executionDuration: 'Execution duration'
290
290
  },
291
+ // ARIA labels for message annotations. The hierarchy trail names the
292
+ // actual path so AT users hear "From: ForEach Loop / Greeter" rather
293
+ // than a generic "hierarchy".
294
+ messageAnnotations: {
295
+ hierarchyOf: ({ path }) => `From: ${path}`
296
+ },
291
297
  sessions: {
292
298
  header: 'Sessions',
293
299
  newSession: 'New Session',
@@ -98,6 +98,13 @@
98
98
  "type": "boolean",
99
99
  "default": false,
100
100
  "description": "Whether to allow multiple selections.\nWhen true, users can select multiple values displayed as tags.\n"
101
+ },
102
+ "params": {
103
+ "type": "object",
104
+ "additionalProperties": {
105
+ "type": "string"
106
+ },
107
+ "description": "Map of URL query parameter names to sibling form field names.\nWhen fetching autocomplete options, the current value of each\nreferenced sibling field is appended as a query parameter.\nWhen any dependency field changes, the autocomplete clears its\ncurrent value and invalidates the suggestion cache.\n"
101
108
  }
102
109
  },
103
110
  "required": [
@@ -453,7 +460,9 @@
453
460
  "completed",
454
461
  "failed",
455
462
  "cancelled",
456
- "skipped"
463
+ "skipped",
464
+ "paused",
465
+ "interrupted"
457
466
  ],
458
467
  "description": "Current execution status of a node"
459
468
  },
@@ -4,10 +4,11 @@
4
4
  */
5
5
  import type { CategoryDefinition } from '../types/index.js';
6
6
  import type { EndpointConfig } from '../config/endpoints.js';
7
+ import type { AuthProvider } from '../types/auth.js';
7
8
  /**
8
9
  * Fetch category definitions from API
9
10
  */
10
- export declare function fetchCategories(endpointConfig: EndpointConfig): Promise<CategoryDefinition[]>;
11
+ export declare function fetchCategories(endpointConfig: EndpointConfig, authProvider?: AuthProvider): Promise<CategoryDefinition[]>;
11
12
  /**
12
13
  * Validate category definitions structure
13
14
  */
@@ -2,17 +2,19 @@
2
2
  * Categories API Service
3
3
  * Handles fetching category definitions from the backend
4
4
  */
5
- import { buildEndpointUrl } from '../config/endpoints.js';
5
+ import { buildEndpointUrl, getEndpointHeaders } from '../config/endpoints.js';
6
6
  import { DEFAULT_CATEGORIES } from '../config/defaultCategories.js';
7
7
  import { logger } from '../utils/logger.js';
8
8
  /**
9
9
  * Fetch category definitions from API
10
10
  */
11
- export async function fetchCategories(endpointConfig) {
11
+ export async function fetchCategories(endpointConfig, authProvider) {
12
12
  try {
13
13
  const url = buildEndpointUrl(endpointConfig, endpointConfig.endpoints.categories);
14
+ const configHeaders = getEndpointHeaders(endpointConfig, 'categories');
15
+ const authHeaders = authProvider ? await authProvider.getAuthHeaders() : {};
14
16
  const response = await fetch(url, {
15
- headers: { 'Content-Type': 'application/json' }
17
+ headers: { ...configHeaders, ...authHeaders }
16
18
  });
17
19
  if (!response.ok) {
18
20
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -4,10 +4,11 @@
4
4
  */
5
5
  import type { PortConfig } from '../types/index.js';
6
6
  import type { EndpointConfig } from '../config/endpoints.js';
7
+ import type { AuthProvider } from '../types/auth.js';
7
8
  /**
8
9
  * Fetch port configuration from API
9
10
  */
10
- export declare function fetchPortConfig(endpointConfig: EndpointConfig): Promise<PortConfig>;
11
+ export declare function fetchPortConfig(endpointConfig: EndpointConfig, authProvider?: AuthProvider): Promise<PortConfig>;
11
12
  /**
12
13
  * Validate port configuration structure
13
14
  */
@@ -2,17 +2,19 @@
2
2
  * Port Configuration API Service
3
3
  * Handles fetching port configuration from the backend
4
4
  */
5
- import { buildEndpointUrl } from '../config/endpoints.js';
5
+ import { buildEndpointUrl, getEndpointHeaders } from '../config/endpoints.js';
6
6
  import { DEFAULT_PORT_CONFIG } from '../config/defaultPortConfig.js';
7
7
  import { logger } from '../utils/logger.js';
8
8
  /**
9
9
  * Fetch port configuration from API
10
10
  */
11
- export async function fetchPortConfig(endpointConfig) {
11
+ export async function fetchPortConfig(endpointConfig, authProvider) {
12
12
  try {
13
13
  const url = buildEndpointUrl(endpointConfig, endpointConfig.endpoints.portConfig);
14
+ const configHeaders = getEndpointHeaders(endpointConfig, 'portConfig');
15
+ const authHeaders = authProvider ? await authProvider.getAuthHeaders() : {};
14
16
  const response = await fetch(url, {
15
- headers: { 'Content-Type': 'application/json' }
17
+ headers: { ...configHeaders, ...authHeaders }
16
18
  });
17
19
  if (!response.ok) {
18
20
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -153,6 +153,7 @@ export declare function mountWorkflowEditor(container: HTMLElement, options?: {
153
153
  endpointConfig?: EndpointConfig;
154
154
  portConfig?: PortConfig;
155
155
  categories?: CategoryDefinition[];
156
+ authProvider?: AuthProvider;
156
157
  }): Promise<MountedFlowDropApp>;
157
158
  /**
158
159
  * Unmount a FlowDrop app
@@ -84,7 +84,7 @@ export async function mountFlowDropApp(container, options = {}) {
84
84
  if (!finalPortConfig && config) {
85
85
  // Try to fetch port configuration from API
86
86
  try {
87
- finalPortConfig = await fetchPortConfig(config);
87
+ finalPortConfig = await fetchPortConfig(config, authProvider);
88
88
  }
89
89
  catch (error) {
90
90
  logger.warn('Failed to fetch port config from API, using default:', error);
@@ -101,7 +101,7 @@ export async function mountFlowDropApp(container, options = {}) {
101
101
  }
102
102
  else if (config) {
103
103
  try {
104
- const fetchedCategories = await fetchCategories(config);
104
+ const fetchedCategories = await fetchCategories(config, authProvider);
105
105
  initializeCategories(fetchedCategories);
106
106
  }
107
107
  catch (error) {
@@ -244,7 +244,7 @@ export async function mountFlowDropApp(container, options = {}) {
244
244
  * @returns Promise resolving to a MountedFlowDropApp instance
245
245
  */
246
246
  export async function mountWorkflowEditor(container, options = {}) {
247
- const { nodes = [], endpointConfig, portConfig, categories } = options;
247
+ const { nodes = [], endpointConfig, portConfig, categories, authProvider } = options;
248
248
  // Create endpoint configuration
249
249
  let config;
250
250
  if (endpointConfig) {
@@ -269,7 +269,7 @@ export async function mountWorkflowEditor(container, options = {}) {
269
269
  if (!finalPortConfig && config) {
270
270
  // Try to fetch port configuration from API
271
271
  try {
272
- finalPortConfig = await fetchPortConfig(config);
272
+ finalPortConfig = await fetchPortConfig(config, authProvider);
273
273
  }
274
274
  catch (error) {
275
275
  logger.warn('Failed to fetch port config from API, using default:', error);
@@ -286,7 +286,7 @@ export async function mountWorkflowEditor(container, options = {}) {
286
286
  }
287
287
  else if (config) {
288
288
  try {
289
- const fetchedCategories = await fetchCategories(config);
289
+ const fetchedCategories = await fetchCategories(config, authProvider);
290
290
  initializeCategories(fetchedCategories);
291
291
  }
292
292
  catch (error) {
@@ -297,6 +297,19 @@ export interface AutocompleteConfig {
297
297
  * @default false
298
298
  */
299
299
  multiple?: boolean;
300
+ /**
301
+ * Map of URL query parameter names to sibling form field names.
302
+ * When fetching autocomplete options, the current value of each
303
+ * referenced sibling field is appended as a query parameter.
304
+ *
305
+ * Example: { "account": "account", "project": "project" }
306
+ * If the "account" field currently holds "my-jira", the URL becomes:
307
+ * /api/jira/issue-types?account=my-jira&q=...
308
+ *
309
+ * When any dependency field changes, the autocomplete clears its
310
+ * current value and invalidates the suggestion cache.
311
+ */
312
+ params?: Record<string, string>;
300
313
  }
301
314
  /**
302
315
  * Dynamic schema endpoint configuration
@@ -118,6 +118,65 @@ export interface PlaygroundMessageMetadata {
118
118
  /** Allow additional properties */
119
119
  [key: string]: unknown;
120
120
  }
121
+ /**
122
+ * A node on the contextual tree the server attaches to a message
123
+ * (e.g. workflow > sub-workflow > iteration). Display-only — this is not a
124
+ * navigation breadcrumb; `href` is intentionally not part of the schema.
125
+ */
126
+ export interface MessageHierarchyItem {
127
+ /** Stable identifier (for keying) */
128
+ id: string;
129
+ /** Display label */
130
+ label: string;
131
+ /** Optional iconify icon id (e.g. 'mdi:graph') */
132
+ icon?: string;
133
+ }
134
+ /**
135
+ * Semantic color hooks for a tag. Map to design tokens in the theme.
136
+ */
137
+ export type MessageTagColor = 'muted' | 'primary' | 'success' | 'warning' | 'error' | 'info';
138
+ /**
139
+ * Visual emphasis for a tag.
140
+ */
141
+ export type MessageTagVariant = 'solid' | 'outline' | 'subtle';
142
+ /**
143
+ * A server-emitted classification chip rendered alongside a message.
144
+ * Tags are unordered and replace any UI-side defaults.
145
+ */
146
+ export interface MessageTag {
147
+ /** Stable identifier (for keying) */
148
+ id: string;
149
+ /** Display label */
150
+ label: string;
151
+ /** Optional iconify icon id */
152
+ icon?: string;
153
+ /** Semantic color hook; defaults to 'muted' if omitted */
154
+ color?: MessageTagColor;
155
+ /** Visual style; defaults to 'subtle' if omitted */
156
+ variant?: MessageTagVariant;
157
+ /** Free-form classifier the server may use for future grouping/filtering */
158
+ type?: string;
159
+ }
160
+ /**
161
+ * Rendering hint the server may set to choose a layout regardless of role.
162
+ *
163
+ * - `bubble`: chat bubble shape (default for user/assistant)
164
+ * - `log`: dense one-liner (default for log role)
165
+ * - `notice`: compact centered notice (default for system role with
166
+ * compactSystemMessages enabled)
167
+ * - `card`: vertical layout — breadcrumb (top), body (middle), tags (bottom)
168
+ *
169
+ * When omitted, the client falls back to the role-based default above.
170
+ */
171
+ export type PlaygroundMessageDisplay = 'bubble' | 'log' | 'notice' | 'card';
172
+ /**
173
+ * Resolve the effective layout for a message. Server-supplied `display` wins;
174
+ * otherwise we fall back to a role-based default. Pure function so the
175
+ * dispatcher and tests can share it.
176
+ */
177
+ export declare function resolveMessageDisplay(message: Pick<PlaygroundMessage, 'role' | 'display'>, options?: {
178
+ compactSystemMessages?: boolean;
179
+ }): PlaygroundMessageDisplay;
121
180
  /**
122
181
  * Message in a playground session
123
182
  *
@@ -159,6 +218,23 @@ export interface PlaygroundMessage {
159
218
  executionId?: string | null;
160
219
  /** Associated node ID (for log/assistant messages) */
161
220
  nodeId?: string | null;
221
+ /**
222
+ * Ordered hierarchy path (e.g. workflow > sub-workflow > iteration).
223
+ * Rendered as a chevron-separated trail. Server-controlled — no
224
+ * client-side derivation.
225
+ */
226
+ hierarchy?: MessageHierarchyItem[];
227
+ /**
228
+ * Server-emitted classification chips rendered with the message.
229
+ * Replace any client-side defaults entirely (no merge).
230
+ */
231
+ tags?: MessageTag[];
232
+ /**
233
+ * Layout hint. When omitted, the client picks a default from the role:
234
+ * - log → 'log', system (when compactSystemMessages) → 'notice',
235
+ * user/assistant → 'bubble'.
236
+ */
237
+ display?: PlaygroundMessageDisplay;
162
238
  /** Additional message metadata */
163
239
  metadata?: PlaygroundMessageMetadata;
164
240
  }
@@ -42,6 +42,20 @@ export function defaultShouldStopPolling(status) {
42
42
  export function defaultIsTerminalStatus(status) {
43
43
  return DEFAULT_TERMINAL_STATUSES.includes(status);
44
44
  }
45
+ /**
46
+ * Resolve the effective layout for a message. Server-supplied `display` wins;
47
+ * otherwise we fall back to a role-based default. Pure function so the
48
+ * dispatcher and tests can share it.
49
+ */
50
+ export function resolveMessageDisplay(message, options = {}) {
51
+ if (message.display)
52
+ return message.display;
53
+ if (message.role === 'system' && options.compactSystemMessages !== false)
54
+ return 'notice';
55
+ if (message.role === 'log')
56
+ return 'log';
57
+ return 'bubble';
58
+ }
45
59
  /**
46
60
  * Metadata field to control Run button state from backend.
47
61
  * When a message contains this field set to true, the Run button becomes enabled.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration.",
4
4
  "license": "MIT",
5
5
  "private": false,
6
- "version": "1.12.0",
6
+ "version": "1.13.0",
7
7
  "author": "Shibin Das (D34dMan)",
8
8
  "bugs": {
9
9
  "url": "https://github.com/flowdrop-io/flowdrop/issues"
@@ -83,6 +83,11 @@
83
83
  "svelte": "./dist/form/code.js",
84
84
  "default": "./dist/form/code.js"
85
85
  },
86
+ "./form/autocomplete": {
87
+ "types": "./dist/form/autocomplete.d.ts",
88
+ "svelte": "./dist/form/autocomplete.js",
89
+ "default": "./dist/form/autocomplete.js"
90
+ },
86
91
  "./form/markdown": {
87
92
  "types": "./dist/form/markdown.d.ts",
88
93
  "svelte": "./dist/form/markdown.js",